Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Empty c.req.raw body for application/x-www-form-urlencoded #2695

Open
LuisMalhadas opened this issue May 17, 2024 · 8 comments
Open

Empty c.req.raw body for application/x-www-form-urlencoded #2695

LuisMalhadas opened this issue May 17, 2024 · 8 comments
Labels

Comments

@LuisMalhadas
Copy link

What version of Hono are you using?

4.3.6

What runtime/platform is your app running on?

bun 1.1.8

What steps can reproduce the bug?

import { HTTPException } from 'hono/http-exception'
import { OpenAPIHono } from '@hono/zod-openapi'

const app = new OpenAPIHono()

app.use(async (c, next) => {
  console.log('Request received:: \n',c.req.raw)
  const bod = await c.req.parseBody()
  console.log('After parse:: ',bod)
  await next()
})

export default {
  port: 4000,
  fetch: app.fetch,
}

What is the expected behavior?

{
  accept: "*/*",
  "accept-encoding": "deflate, gzip, br",
  authorization: "-------",
  "content-length": "125073",
  "content-type": "application/x-www-form-urlencoded",
  host: "api-service:4000",
  "user-agent": "MoodleBot/4.3 (+http://localhost:8080)",
  client_id: "nkalnw0ov",
}
�ftypM4A M4A isommp42�mdat�� ...

What do you see instead?

Request (0 KB) {
method: "POST",
url: "http://api-service:4000/endpoint",
headers: Headers {
    "host": "api-service:4000",
     "accept": "*/*",
     "accept-encoding": "deflate, gzip, br",
     "authorization": "------",
     "user-agent": "MoodleBot/4.3 (+http://localhost:8080)",
     "content-length": "125073",
     "content-type": "application/x-www-form-urlencoded",
   }
}
{}

Additional information

I can get the body using plain bun.

@EdamAme-x
Copy link
Contributor

looks no problem...?

 constructor(
    request: Request,
    path: string = '/',
    matchResult: Result<[unknown, RouterRoute]> = [[]]
  ) {
    this.raw = request
    this.path = path
    this.#matchResult = matchResult
    this.#validatedData = {}
  }
 private dispatch(
    request: Request,
    executionCtx: ExecutionContext | FetchEventLike | undefined,
    env: E['Bindings'],
    method: string
  ): Response | Promise<Response> {
    // Handle HEAD method
    if (method === 'HEAD') {
      return (async () =>
        new Response(null, await this.dispatch(request, executionCtx, env, 'GET')))()
    }

    const path = this.getPath(request, { env })
    const matchResult = this.matchRoute(method, path)

    const c = new Context(new HonoRequest(request, path, matchResult), {
      env,
      executionCtx,
      notFoundHandler: this.notFoundHandler,
    })
...

@EdamAme-x
Copy link
Contributor

That problem is only in OpenAPIHono enviroment?

@EdamAme-x
Copy link
Contributor

@EdamAme-x
Copy link
Contributor

hi @LuisMalhadas
i found out

async function parseFormData<T extends BodyData = BodyData>(
  request: HonoRequest | Request,
  options: ParseBodyOptions
): Promise<T> {
  const formData = await (request as Request).formData()

  if (formData) {
    return convertFormDataToBodyData<T>(formData, options)
  }

  // cause problem
  return {} as T
}

@EdamAme-x
Copy link
Contributor

EdamAme-x commented May 25, 2024

request is avaible.
but, parsed formData is non-avaible.

@EdamAme-x
Copy link
Contributor

It is likely that the formData you submitted is corrupted..
If not, please tell me additional information.

function convertFormDataToBodyData<T extends BodyData = BodyData>(
  formData: FormData,
  options: ParseBodyOptions
): T {
  const form: BodyData = {}

  formData.forEach((value, key) => {
    const shouldParseAllValues = options.all || key.endsWith('[]')

    if (!shouldParseAllValues) {
      form[key] = value
    } else {
      handleParsingAllValues(form, key, value)
    }
  })

  return form as T
}

@LuisMalhadas
Copy link
Author

LuisMalhadas commented May 26, 2024

@EdamAme-x First of all thanks for paying attention to it and replying.
It could be... I have actually encountered similar issue: lau1944/bunrest#46
And with so many attempts at this, I may confuse somethings.

Either way...

I feel it is an issue with OpenAPIHono, as well.
The following is the definition I use for this particular endpoint:

speech2text_app.openapi(
    createRoute({
      method: 'post',
      path: '',
      description: 'Transcribes an audio file to text',
      request: {
        params: z.object({
            language: z.string().optional().openapi({ description: 'The language of the output text.'})
        }),
        body: {
            content: {
                'application/x-www-form-urlencoded': {
                    schema: z.string().openapi({ format: 'binary', description: 'The audio file to transcribe' })
                }
            },
            required: true
        },
        headers: auth_schema
      },
      responses: {
        200: {
          description: 'The full text of the audio file',
          content: {
            'application/json': {
              schema: z.object({
                text: z.string().openapi({ description: 'The transcribed text' })
              })
            }
          }
        },
        ...

To give more details
This is the php code that I am using to make the request:

...
    $endpoint = $this->base_url."/speech2text";
    $options = array();
    $header = array(
        'Authorization:' . .......,
        'mimetype:' . $filemime,
    );

    $curl = new \curl(['ignoresecurity' => true]);
    $curl->setHeader($header);
    $curl->setopt($options);
    $response = $curl->post($endpoint, base64_encode($curlFile));
    ...

It is an audio file.
In Bun I receive it like so:

...
    const audiodata = new FormData()
    const audio = atob(req.body??'')
    const buffer = new ArrayBuffer(audio.length)
    const view = new Uint8Array(buffer)
    for (let i = 0; i < audio.length; i++) {
        view[i] = audio.charCodeAt(i)
    }
    const blob = new Blob([view], { type: req.headers?.mimetype as string })
    audiodata.append('audio_file', blob, req.headers?.mimetype as string)

That is working (for now), but implies I load the file all into memory.
The framework in use, on the php stack side, appears to be quite limited, and this may be the only method available to send...
Nevertheless using the HonoOpenAPI swagger frontend:

- I have disabled the authorisation header here (due to another issue with the swagger frontend and headers) -

2024-05-26T08:34:49.407Z   <-- POST /speech2text
Request received:: 
 Request (0 KB) {
  method: "POST",
  url: "http://localhost:4007/speech2text",
  headers: Headers {
    "host": "localhost:4007",
    "accept": "application/json",
    "accept-encoding": "gzip, deflate",
    "accept-language": "en-GB,en;q=0.9",
    "sec-fetch-mode": "cors",
    "content-type": "application/x-www-form-urlencoded",
    "origin": "http://localhost:4007",
    "user-agent": "-----------",
    "referer": "http://localhost:4007/ui",
    "content-length": "0",
    "connection": "keep-alive",
    "cookie": "-------",
    "sec-fetch-dest": "empty",
    "sec-fetch-site": "same-origin",
  }
}
in c.req.parseBody() comes {}

2024-05-26T08:34:49.415Z Request received POST http://localhost:4007/speech2text 
2024-05-26T08:34:49.417Z   --> POST /speech2text 400 10ms

{
  "success": false,
  "error": {
    "issues": [
      {
        "code": "invalid_type",
        "expected": "string",
        "received": "object",
        "path": [],
        "message": "Expected string, received object"
      }
    ],
    "name": "ZodError"
  }
}

Shouldn't this work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants