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

serveStatic w/ compress middleware issue #104

Closed
luwes opened this issue Nov 28, 2023 · 9 comments · Fixed by #108
Closed

serveStatic w/ compress middleware issue #104

luwes opened this issue Nov 28, 2023 · 9 comments · Fixed by #108

Comments

@luwes
Copy link

luwes commented Nov 28, 2023

In my testing the compress middleware made a serveStatic HTML page return plain text text/plain;charset=UTF-8.
https://hono.dev/middleware/builtin/compress

@sebastianwessel
Copy link

sebastianwessel commented Dec 9, 2023

I can confirm, that there is an issue with the response content-type header, as soon as compression is enabled.
It also happens when you simply send a JSON response.

Minimal example:

import { serve } from '@hono/node-server'
import { Hono } from 'hono'
import { compress } from 'hono/compress'

const app = new Hono()

app.use('*', compress())

app.get('/', (c) =>
  c.json({
    some: 'value',
  }),
)

serve({
  fetch: app.fetch,
  port: 3000,
})

response:
content-encoding: gzip
content-type: text/plain;charset=UTF-8

Tried it with node 20.8 and 20.10

The issue seems not to be present when with Deno.
As Bun isn't supporting compress right now, I couldn't test it.

I had a quick look into compress middleware and I didn't recognize something, that might be the root cause.
So, I think the issue is somewhere in the node-server package.

From quick look into the code, I guess the code, the issue relies on this code here:

if (typeof body === 'string' || body instanceof ReadableStream) {

and here:
export const buildOutgoingHttpHeaders = (headers: Headers): OutgoingHttpHeaders => {

The mapping from the current headers to the final headers is messed up in compression with:
https://github.com/honojs/hono/blob/75dbd4dc229335d473b809b0f2190e3b4044b9bc/src/middleware/compress/index.ts#L21

I guess the headers simply have different types, but I'm too unfamiliar with hono code base, to provide a propper fix atm.

@yusukebe
Copy link
Member

@luwes @sebastianwessel Thank you for raising the issue.

Hi @usualoma, in the fowllowing case, test will fail. Can you see it?

describe('content-type', () => {
  const app = new Hono()
  app.use('*', async (c, next) => {
    await next()
    c.res = new Response('', c.res)
    c.res.headers // If this is set, test fails
  })

  app.get('/json', async (c) => {
    return c.json({})
  })

  it('Should return 200 response - GET /json', async () => {
    const server = createAdaptorServer(app)
    const res = await request(server).get('/json')
    expect(res.status).toBe(200)
    expect(res.headers['content-type']).toMatch(/application\/json/)
  })
})

@usualoma
Copy link
Member

It appears to be a bug in the Response object. It can be fixed. Please wait a moment.

@yusukebe
Copy link
Member

@usualoma

Thanks!

@yusukebe
Copy link
Member

yusukebe commented Dec 11, 2023

Hi @luwes @sebastianwessel

Thanks to @usualoma , this issue is fixed in latest version v1.3.2. Give it a try!

@sebastianwessel
Copy link

I did a quick test, and it seems to work as expected.
Thanks for the quick fix 🙏.

@luwes
Copy link
Author

luwes commented Dec 11, 2023

Thank you for the fix!

I tested it out but not all statics are compressed it seems.

Using

  app.use('*', serveStatic({ root: dir }));
  app.use('*', compress());

/favicon.ico is gzipped

SCR-20231211-cs8

/test/test.js is not

SCR-20231211-cst

test/index.html is not

SCR-20231211-cru

If I try to reverse the middleware:

  app.use('*', compress());
  app.use('*', serveStatic({ root: dir }));

I get an error
SCR-20231211-cr0

@yusukebe
Copy link
Member

It can be reproduced by this minimal code:

app.get('/', () => {
  const stream = new ReadableStream({
    start(controller) {
      const encoder = new TextEncoder()
      controller.enqueue(encoder.encode('hello'))
      controller.close()
    },
  })
  const originalResponse = new Response(stream)
  const compStream = new CompressionStream('gzip')
  const res = new Response(originalResponse.body!.pipeThrough(compStream), originalResponse)
  res.headers.set('Content-Encoding', 'gzip')
  return res
})

@usualoma When you have time, please take a look.

@yusukebe yusukebe reopened this Dec 11, 2023
@yusukebe
Copy link
Member

@luwes

It's fixed thanks to @usualoma and I've released new version v1.3.3 which includes the fix. Take a look!

@luwes luwes closed this as completed Dec 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants