MartinSahlen / cloud-functions-python

Get some python in google cloud functions
203 stars 29 forks source link

TypeError: req.set is not a function #42

Closed santhoshdc1590 closed 6 years ago

santhoshdc1590 commented 6 years ago

I used the function.py 's handle_http as my cloud function to be built

def handle_http(req):

    biquery_client = bigquery.Client(credentials=get_credentials())

    if req.method == 'OPTIONS':
        return JsonResponse({"status":"success"},status=200,safe=False)

    if req.method == 'GET':
        print("Body: ",type(req.GET.getlist('image')))
        print(type(req.GET.getlist('image')),req.GET.getlist('image'))

        i=req.GET.getlist('image')
        i=''.join(str(e) for e in i)

        print(i)

        rID = req.GET.getlist('refID')
        rTable = req.GET.getlist('refTable')
        print(rID," ",rTable)
        ########### main custom function. which I am calling ##################
        mainP(i,rID,rTable)

        return Response(
            status_code=200,
            body={'key': 2},
            headers={'content-type': 'application/json'},
        )

handle_http_event(handle_http)

This was the index.js generated after the build below

var googleAuth = require('google-auto-auth')();
//Handle Background events according to spec
function shimHandler(data) {
  return new Promise((resolve, reject) => {
    googleAuth.getToken(function (err, oauthToken) {
      if (err) {
        reject()
      } else {
        const p = require('child_process').execFile('./dist/func/func', {
          env: Object.assign(process.env, {
            'GOOGLE_OAUTH_TOKEN': oauthToken,
          })
        });
        var lastMessage;
        p.stdin.setEncoding('utf-8');
        //Log standard err messages to standard err
        p.stderr.on('data', (err) => {
          console.error(err.toString());
        })
        p.stdout.on('data', (out) => {
          console.log(out.toString());
          lastMessage = out;
        })
        p.on('close', (code) => {
          if (code !== 0) {
            //This means the shim failed / panicked. So we reject hard.
            reject();
          } else {
            // Resolve the promise with the latest output from stdout
            // In case of shimming http, this is the response object.
            resolve(lastMessage);
          }
        });
        //Write the object/message/request to the shim's stdin and signal
        //End of input.
        p.stdin.write(JSON.stringify(data));
        p.stdin.end();
      }
    });
  });
}

//Handle http request
function handleHttp(req, res) {
  var requestBody;
  switch (req.get('content-type')) {
    case 'application/json':
      requestBody = JSON.stringify(req.body);
      break;
    case 'application/x-www-form-urlencoded':
      //The body parser for cloud functions does this, so just play along
      //with it, sorry man! Maybe we should construct some kind of proper
      //form request body? or not. let's keep it this way for now, as
      //This is how cloud functions behaves.
      req.set('content-type', 'application/json')
      requestBody = JSON.stringify(req.body);
      break;
    case 'application/octet-stream':
      requestBody = req.body;
      break;
    case 'text/plain':
      requestBody = req.body;
      break;
  }

  var fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;

  var httpRequest = {
    'body': requestBody,
    'headers': req.headers,
    'method': req.method,
    'remote_addr': req.ip,
    'url': fullUrl
  };

  shimHandler(httpRequest)
  .then((result) => {
    data = JSON.parse(result);
    res.status(data.status_code);
    res.set(data.headers)
    res.send(data.body);
  })
  .catch(() => {
    res.status(500).end();
  })
}

//
exports['handle_http'] = function(req, res) {
  return handleHttp(req, res);
}//

When testing the url generated locally using POSTMAN

I'm getting this error

{"stack":"TypeError: req.set is not a function\n at handleHttp (/Users/santhoshdc/Documents/LaunchTestOne/cloudfn/target/index.js:55:11)\n at exports.handle_http (/Users/santhoshdc/Documents/LaunchTestOne/cloudfn/target/index.js:90:10)\n at app.use (/Users/santhoshdc/.nvm/versions/node/v9.8.0/lib/node_modules/@google-cloud/functions-emulator/src/supervisor/worker.js:142:11)\n at Layer.handle [as handle_request] (/Users/santhoshdc/.nvm/versions/node/v9.8.0/lib/node_modules/@google-cloud/functions-emulator/node_modules/express/lib/router/layer.js:95:5)\n at trim_prefix (/Users/santhoshdc/.nvm/versions/node/v9.8.0/lib/node_modules/@google-cloud/functions-emulator/node_modules/express/lib/router/index.js:317:13)\n at /Users/santhoshdc/.nvm/versions/node/v9.8.0/lib/node_modules/@google-cloud/functions-emulator/node_modules/express/lib/router/index.js:284:7\n at Function.process_params (/Users/santhoshdc/.nvm/versions/node/v9.8.0/lib/node_modules/@google-cloud/functions-emulator/node_modules/express/lib/router/index.js:335:12)\n at next (/Users/santhoshdc/.nvm/versions/node/v9.8.0/lib/node_modules/@google-cloud/functions-emulator/node_modules/express/lib/router/index.js:275:10)\n at app.use (/Users/santhoshdc/.nvm/versions/node/v9.8.0/lib/node_modules/@google-cloud/functions-emulator/src/supervisor/worker.js:114:7)\n at Layer.handle [as handle_request] (/Users/santhoshdc/.nvm/versions/node/v9.8.0/lib/node_modules/@google-cloud/functions-emulator/node_modules/express/lib/router/layer.js:95:5)","message":"req.set is not a function","name":"TypeError"}

@MartinSahlen or anyone else who knows what's going on here, help please?

MartinSahlen commented 6 years ago

What is going on is that this line: https://github.com/MartinSahlen/cloud-functions-python/blob/master/cloudfn/template/index.js#L55 tries to call the function set which does not seem exist on the request object and thereby fails. Likely because noone so far has tried using a cloud function for posting form data - or it existed in a previous version of express and you seem to be using the emulator. It should be an easy fix to go around this bug - feel free to submit a pull request to fix this @santhoshdc1590 👍

santhoshdc1590 commented 6 years ago

I'm new to this web thing. I do understand that the req.set doesn't exist for the program to call it. I'm unable to find an alternative for req.set. Yes I'm using emulator. express is new to me again. Would really appreciate if you could guide me through this 😅

MartinSahlen commented 6 years ago

Unfortunately I do not have a lot of time to provide guidance but I can try to fix the bug when I can carve out some time.

santhoshdc1590 commented 6 years ago

@MartinSahlen I couldn't find any function called req.set in express2.x or express3.x can I use req.accepts('application/json') instead to req.set('content-type', 'application/json') from express4.x?

MartinSahlen commented 6 years ago

I think it can be just removed, is it is an error on my end as I said. Since JavaScript is not statically typed this error is not caught and occurs at runtime when posting data using forms.

santhoshdc1590 commented 6 years ago

I commented the req.set, built and installed cloud function python again.

Built and deployed the function similarly locally.

Now I am not getting 500 internal server error on POSTMAN and black screen in browser

I tried changing https://github.com/MartinSahlen/cloud-functions-python/blob/master/cloudfn/template/index.js#L55 and checked what's happening

This is the changed program

var googleAuth = require('google-auto-auth')();
//Handle Background events according to spec
function shimHandler(data) {
  return new Promise((resolve, reject) => {
    googleAuth.getToken(function (err, oauthToken) {
      if (err) {
        reject()
      } else {
        const p = require('child_process').execFile('./dist/{{config["output_name"]}}/{{config["output_name"]}}', {
          env: Object.assign(process.env, {
            'GOOGLE_OAUTH_TOKEN': oauthToken,
          })
        });
        var lastMessage;
        p.stdin.setEncoding('utf-8');
        //Log standard err messages to standard err
        p.stderr.on('data', (err) => {
          console.error(err.toString());
        })
        p.stdout.on('data', (out) => {
          console.log(out.toString());
          lastMessage = out;
        })
        p.on('close', (code) => {
          if (code !== 0) {
            //This means the shim failed / panicked. So we reject hard.
            reject();
          } else {
            // Resolve the promise with the latest output from stdout
            // In case of shimming http, this is the response object.
            resolve(lastMessage);
          }
        });
        //Write the object/message/request to the shim's stdin and signal
        //End of input.
        p.stdin.write(JSON.stringify(data));
        p.stdin.end();
      }
    });
  });
}

//Handle http request
function handleHttp(req, res) {
  var requestBody;
  console.log('hello')
  console.log(req.get('content-type'))
  switch (req.get('content-type')) {
    case 'application/json':
      requestBody = JSON.stringify(req.body);
      break;
    case 'application/x-www-form-urlencoded': //application/x-www-form-urlencoded
      //The body parser for cloud functions does this, so just play along
      //with it, sorry man! Maybe we should construct some kind of proper
      //form request body? or not. let's keep it this way for now, as
      //This is how cloud functions behaves.
      //req.setHeader('content-type', 'application/json')

      requestBody = JSON.stringify(req.body);
      console.log('this should be the case, the result of switch statement');
      requestBody = JSON.stringify(req.body);
      console.log('Request body is: ');
      console.log(requestBody);
      break;
    case 'application/octet-stream':
      requestBody = req.body;
      break;
    case 'text/plain':
      requestBody = req.body;
      break;
  }

  var fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;

  var httpRequest = {
    'body': requestBody,
    'headers': req.headers,
    'method': req.method,
    'remote_addr': req.ip,
    'url': fullUrl
  };

  shimHandler(httpRequest)
  .then((result) => {
    console.log('should come here')
    data = JSON.parse(result);
    res.status(data.status_code);
    res.set(data.headers)
    res.send(data.body);
  })
  .catch((e) => {
    console.log(e);
    console.log('some error has occurred');
    res.status(500).end();
  })
}

//{% if config["trigger_http"] %}
exports['{{config["function_name"]}}'] = function(req, res) {
  return handleHttp(req, res);
}//{% else %}
exports['{{config["function_name"]}}'] = function(event, callback) {
  return shimHandler(event.data).then(function() {
    callback();
  }).catch(function() {
    callback(new Error("Function failed"));
  });
}//{% endif %}

This is what I get on running logs using functions logs read

2018-03-23T13:37:16.389Z - info: User function triggered, starting execution 2018-03-23T13:37:16.389Z - info: hello application/x-www-form-urlencoded this should be the case, the result of switch statement Request body is: {} 2018-03-23T13:37:16.410Z - info: undefined 2018-03-23T13:37:16.411Z - info: some error has occurred 2018-03-23T13:37:16.415Z - info: Execution took 29 ms, user function completed successfully