parcel-bundler/parcel

dev server: no Content-Length header on HEAD requests

alukach opened this issue ยท 4 comments

๐Ÿ› bug report

When running the dev server, it appears that the server does not respond with a Content-Length when responding to a HEAD request.

๐ŸŽ› Configuration (.babelrc, package.json, cli command)

{
  "name": "sqlite-playground",
  "source": "src/index.html",
  "browserslist": "> 0.5%, last 2 versions, not dead",
  "scripts": {
    "start": "parcel",
    "build": "parcel build"
  },
  "devDependencies": {
    "buffer": "^6.0.3",
    "crypto-browserify": "^3.12.0",
    "events": "^3.3.0",
    "parcel": "latest",
    "path-browserify": "^1.0.1",
    "process": "^0.11.10",
    "stream-browserify": "^3.0.0"
  },
  "dependencies": {
    "sql.js": "^1.7.0",
    "sql.js-httpvfs": "^0.8.11"
  }
}

๐Ÿค” Expected Behavior

HEAD response contains Content-Length & Accept-Ranges headers

$ curl http://localhost:1234 --head
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, PUT, PATCH, POST, DELETE
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Content-Type
Accept-Ranges: bytes
Cache-Control: max-age=0, must-revalidate
Content-Length: 106
Date: Fri, 19 Aug 2022 03:02:58 GMT
Connection: keep-alive
Keep-Alive: timeout=5

๐Ÿ˜ฏ Current Behavior

HEAD response does not contain Content-Length or Accept-Ranges headers

$ curl http://localhost:1234 --head
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, PUT, PATCH, POST, DELETE
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Content-Type
Cache-Control: max-age=0, must-revalidate
Date: Fri, 19 Aug 2022 03:32:02 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Note that responses `GET` requests _do_ contain `Content-Length` and `Accept-Ranges` headers
curl http://localhost:1234 --verbose                                            
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 1234 (#0)
> GET / HTTP/1.1
> Host: localhost:1234
> User-Agent: curl/7.64.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, HEAD, PUT, PATCH, POST, DELETE
< Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Content-Type
< Cache-Control: max-age=0, must-revalidate
< Content-Length: 98
< Content-Disposition: inline; filename="index.html"
< Accept-Ranges: bytes
< Last-Modified: Fri, 19 Aug 2022 03:40:25 GMT
< Content-Type: text/html; charset=utf-8
< Date: Fri, 19 Aug 2022 03:40:41 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< 
<html><meta charset="utf-8"><title>example</title>
* Connection #0 to host localhost left intact
<script src="/tmp.3464ddca.js"></script></html>* Closing connection 0

๐Ÿ’ Possible Solution

๐Ÿ”ฆ Context

I'm using a library that makes range requests to a static file (a SQLite database). It first makes a HEAD request to understand the size of the file but throws an error being that the Content-Length header is not present in the HEAD request's response.

๐Ÿ’ป Code Sample

echo "<html><meta charset=utf-8><title>example</title>" > index.html
npx parcel index.html
curl http://localhost:1234 --head

๐ŸŒ Your Environment

Software Version(s)
Parcel .2.7.0
Node v16.13.0
npm/Yarn 1.22.17
Operating System OSX

Relevant code:

if (req.method === 'HEAD') {
res.end();
return;
}
if (fresh(req.headers, {'last-modified': stat.mtime.toUTCString()})) {
res.statusCode = 304;
res.end();
return;
}
return serveHandler(
req,
res,
{
public: root,
cleanUrls: false,
},
{
lstat: path => fs.stat(path),
realpath: path => fs.realpath(path),
createReadStream: (path, options) => fs.createReadStream(path, options),
readdir: path => fs.readdir(path),
},

@mischnic A quick test shows that if I comment out lines 327-330, the server behaves as I expect:

โžค curl --head http://localhost:1234/
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, PUT, PATCH, POST, DELETE
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Content-Type
Cache-Control: max-age=0, must-revalidate
Content-Length: 302
Content-Disposition: inline; filename="index.html"
Accept-Ranges: bytes
Last-Modified: Wed, 24 Aug 2022 04:04:07 GMT
Content-Type: text/html; charset=utf-8
Date: Wed, 24 Aug 2022 04:11:17 GMT
Connection: keep-alive
Keep-Alive: timeout=5

However, I admit that I don't know if there are negative repercussions to such a change.

I'm not sure why that check is there.

As long as


handles HEAD correctly, removing that should be fine.

(And another question is how a head request should behave with if-modified-since (that other if block)

another question is how a head request should behave with if-modified-since (that other if block)

Reading RF 7231, I see:

The HEAD method is identical to GET except that the server MUST NOT send a message body in the response [...] The server SHOULD send the same header fields in response to a HEAD request as it would have sent if the request had been a GET, except that the payload header fields (Section 3.3) MAY be omitted.

Reading the If-Modified-Since header docs, I see:

Unlike If-Unmodified-Since, If-Modified-Since can only be used with a GET or HEAD.

These details makes me think that the HEAD request should be handled exactly as a GET request should be handled, with the following exceptions:

  1. No body should be returned.
  2. Payload header fields may be omitted.

Point 2 makes me think this issue is actually a feature request rather than a bug report, but nonetheless I think the idea still stands.