dougmoscrop / serverless-http

Use your existing middleware framework (e.g. Express, Koa) in AWS Lambda 🎉
Other
1.71k stars 164 forks source link

Exception: Error: Unexpected request.body type: undefined #255

Open Vikas252 opened 1 year ago

Vikas252 commented 1 year ago

With the same code of the issue https://github.com/dougmoscrop/serverless-http/issues/252 I'm getting an request.body error which says undefined.

The context parameter log:

bindings: {
req: {
       method: 'GET',
       url: 'http://localhost:7071/api/serverless',
       originalUrl: 'http://localhost:7071/api/serverless',
       headers: [Object],
       query: {},
       params: {},
       body: undefined,
       rawBody: undefined
     }

the only one api im using:

router.get("/api/serverless", (req, res) => {
  res.json("hi")
  console.log("hi");
})

the error:

[2022-10-31T07:15:51.445Z] Executed 'Functions.api' (Failed, Id=b1dad52a-5405-4022-bb81-b8be504b7aab, Duration=152ms)
[2022-10-31T07:15:51.445Z] System.Private.CoreLib: Exception while executing function: Functions.api. System.Private.CoreLib: Result: Failure
[2022-10-31T07:15:51.446Z] Exception: Error: Unexpected request.body type: undefined
[2022-10-31T07:15:51.446Z] Stack: Error: Unexpected request.body type: undefined
[2022-10-31T07:15:51.446Z]     at requestBody (/workspaces/serverless/node_modules/serverless-http/lib/provider/azure/create-request.js:25:11)
[2022-10-31T07:15:51.446Z]     at module.exports (/workspaces/serverless/node_modules/serverless-http/lib/provider/azure/create-request.js:32:18)
[2022-10-31T07:15:51.446Z]     at /workspaces/serverless/node_modules/serverless-http/lib/provider/azure/index.js:8:25
[2022-10-31T07:15:51.446Z]     at Object.module.exports.handlertest (/workspaces/serverless/src/handlers/hello.js:47:25)
[2022-10-31T07:15:51.447Z]     at msg (/workspaces/serverless/node_modules/azure-functions-core-tools/bin/workers/node/dist/src/worker-bundle.js:17323:26)
[2022-10-31T07:15:51.447Z]     at WorkerChannel.invocationRequest (/workspaces/serverless/node_modules/azure-functions-core-tools/bin/workers/node/dist/src/worker-bundle.js:42009:28)
[2022-10-31T07:15:51.447Z]     at ClientDuplexStream.<anonymous> (/workspaces/serverless/node_modules/azure-functions-core-tools/bin/workers/node/dist/src/worker-bundle.js:41779:30)
[2022-10-31T07:15:51.447Z]     at ClientDuplexStream.emit (events.js:400:28)
[2022-10-31T07:15:51.447Z]     at addChunk (internal/streams/readable.js:293:12)
[2022-10-31T07:15:51.447Z]     at readableAddChunk (internal/streams/readable.js:267:9).

I'm also trying to implement and debug as the azure is untested

Extra added line to debug in hello.js :

router.use(compression())
router.use(cors())
router.use(bodyParser.json())
router.use(bodyParser.urlencoded({ extended: true }))

this didnt resolve the issue

Vikas252 commented 1 year ago

Update: Its weird but when i send a raw data to the above api im getting the response hi

2022-10-31

i had changed the serverless.yml in this case to:

service: serverless
provider:
  name: azure
  region: West US 2
  runtime: nodejs14
  environment:
    VARIABLE_FOO: foo
plugins:
  - serverless-azure-functions
package:
  patterns:
    - "!local.settings.json"
    - "!.vscode/**"
functions:
  serverless:
    handler: src/handlers/hello.handlertest
    events:
      - http: 
          path: /
          method: ANY
          cors: true
      - http: 
          path: /{any+}
          method: ANY
          cors: true

it was only working with /api/serverless any other routes its showing 404 not found

is it broken or im doing it wrong

TaylorBeeston commented 1 year ago

I was able to get past this like this:

req.rawBody = req.rawBody || '';

context.res = await handler(context, req);

Also: In the serverless.yaml, you'll want to change path to route and (I think) get rid of that + after any!

Vikas252 commented 1 year ago

Thank you for the response @TaylorBeeston

After updating the code now its showing 204 No Content in the postman, and no response is been shown.

and had to update the serverless.yml to this

service: serverless
provider:
  name: azure
  region: West US 2
  runtime: nodejs14
  environment:
    VARIABLE_FOO: foo
plugins:
  - serverless-azure-functions
package:
  patterns:
    - "!local.settings.json"
    - "!.vscode/**"
functions:
  api:
    handler: dist/handler.handler
    events:
      - http: ANY /
        name: res
        route: '{*segments}'
        authLevel: anonymous
      - http: 'ANY {proxy}'
        x-azure-settings:
          direction: out
          name: $return

because i was getting 404 error in the old yml file any help is appreciated been stuck here since forever. Researching

TaylorBeeston commented 1 year ago

Okay, I've done a bunch of work myself getting this to run, and I've finally just gotten it working correctly both offline and deployed!

To start, I've started using ESBuild to drastically lower the cycle time when deploying. I did that by first adding esbuild:

pnpm i -D esbuild rimraf # You don't have to use pnpm, but I highly recommend it!

Then adding a build script:

// esbuild.mjs
import { build } from 'esbuild';

const startTime = Date.now();

console.log('🎁 Building main bundle...');

const finalBuildObj = {
    entryPoints: ['src/index.ts'], // add whatever src files your handler is in!
    platform: 'node',
    bundle: true,
    format: 'cjs',
    outfile: 'dist/index.js',
    target: 'node12',
    plugins: [],
    external: [],
    minify: true,
};

if (process.env.NODE_ENV !== 'production') {
    finalBuildObj.sourcemap = 'inline';
    finalBuildObj.minify = false;
}

build(finalBuildObj).then(() => {
    console.log(`🎁 Done building main bundle! (${Date.now() - startTime}ms)`);
});

Then with that in place, I wrapped the sls commands to add a build step first:

// package.json
  "scripts": {
    "test": "echo \"No tests yet...\"",
    "build": "rimraf dist && node esbuild.mjs",
    "sls-deploy": "pnpm build && sls deploy",
    "sls-offline": "pnpm build && sls offline",
    "start": "pnpm sls-offline"
  },

(Again, you don't have to use pnpm. Just replace pnpm with npm run in the scripts)

With this in place, you'll use pnpm start or pnpm sls-offline to start offline, and pnpm sls-deploy to deploy.

The last thing to do is update your serverless file to only include the built code:

# serverless.yaml
package:
  patterns:
    - "!**"
    - dist/**
    - app/**
    - host.json
    - local.settings.json

This change brought my "minimal" azure.zip from ~500MB down to 704KB! It also allows you to write your function in TS if that's your thing.

One more thing: You'll now be using the built js file inside of dist and not the source file inside of src, so you'll need to update your function in your serverless.yaml to point to that:

# serverless.yaml
functions:
  app:
    handler: dist/index.app

If that part is confusing to you, I can elaborate by showing you my directory structure, as well as what gets put inside of dist for me!


Okay, with ESBuild out of the way, it should be much easier for you to start testing a config that works for you. I found that adding the manual x-azure-settings event was giving me 204s locally, just like you, so I axed it. I also found that if I tried changing the routePrefix in host.json, it would break when deploying, so make sure that file just looks like this:

// host.json
{
    "version": "2.0"
}

So, in the end, my serverless.yaml file looks like this (comments stripped)

service: azure

frameworkVersion: '3'

provider:
  name: azure
  region: West US 2
  runtime: nodejs12

plugins:
  - serverless-azure-functions

package:
  patterns:
    - "!**"
    - dist/**
    - app/**
    - host.json
    - local.settings.json

functions:
  app:
    handler: dist/index.app
    events:
      - http: true
        route: "{test}"
        method: ANY
        cors: true
        authLevel: anonymous

I have a simple express app that exposes /api/hello, /api/nice, and /api/:test, echoing different things for each route, and they all work successfully for me! Hope this helps you solve your problem!

TaylorBeeston commented 1 year ago

Just to be extra helpful, I decided to upload my code here!

Vikas252 commented 1 year ago

Thank you for the response and for a working example @TaylorBeeston currently i have changed the folder structure a little bit and goes like app.ts has server initialization with express handler.ts has the azure serverless-http then routes.ts has all the routes when i implemented the same as you said with all the configs i'm getting can not GET /api/sls

app.ts

"use strict"
//const serverless = require('serverless-http')
import express from "express"
//import compression from "compression"
import cors from "cors"
import bodyParser from "body-parser"

import setRoutes from "./routes"

const app = express()

app.use(cors())
app.set("port", 3000)
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*")
  res.header(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept"
  )
  next()
})

async function main(): Promise<any> {
  try {
    setTimeout(() => {
      setRoutes(app)
      app.listen(app.get("port"), () => {
        console.log(`listening on port ${app.get("port")}`)
      })
      console.log("Testing")
    }, 30000)
  } catch (err) {
    console.error(err)
  }
}
main()

export { app, express }

handler.ts

import serverless from 'serverless-http';
//import { app } from "./app";

import express, { Router, json } from "express"

const app = express()

const router = Router();

router.get("/hello", (req, res) => {
    console.log("You are there");
    return res.status(200).send("Nice!");
});

const handler = serverless(app,{provider:'azure'})
module.exports.app = async (context, req) => {
  req.rawBody = req.rawBody || '';
  context.res = await handler(context, req);
}

routes.ts:

//import { express } from "./app"

import express from "express"
import {app} from "./app"

function setRoutes(app): void {
  const router = express.Router()
  router.route("/serverless").get((req, res) => {
    return res.json("hi")
  })

  router.route("/sls").get((req, res) => {
    return res.json("bye")
  })

  app.use("/api", router)
}

export default setRoutes

not able to hit either hello in the handler.ts or routes in routes.ts

meanwhile i'll be also trying to rectify what silly mistake i would had done

Vikas252 commented 1 year ago

Update: The routes are working in postman but not in normal URL's