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

handle_errors 200 empty body #6422

Open
odama626 opened this issue Jun 27, 2024 · 15 comments
Open

handle_errors 200 empty body #6422

odama626 opened this issue Jun 27, 2024 · 15 comments
Labels
needs info 📭 Requires more information

Comments

@odama626
Copy link

I think I found kind of an odd bug. I am using caddy as a static file server for a SPA, so if I get an error, I just want it to try again at the root directory -- mostly for index.html and favicon

this works, returns index page but the status is still 404 on the successful document, odd but ok looking at the docs seems to be by design?

handle_errors {
  rewrite * /
  file_server
}

This one however -- it works on the initial page load, but if you refresh the page it returns an empty body for the page request and the index.html file for the favicon

handle_errors {
  rewrite * /
  file_server {
    status 200
  }
}

initial page load:
image
image

page refresh:
image

when doing a hard refresh you again get the correct response

I am assuming this is a bug with some kind of caching key mechanism in handle_errors?

@mholt
Copy link
Member

mholt commented Jun 28, 2024

Thanks for opening an issue! We'll look into this.

It's not immediately clear to me what is going on, so I'll need your help to understand it better.

Ideally, we need to be able to reproduce the bug in the most minimal way possible using the latest version of Caddy. This allows us to write regression tests to verify the fix is working. If we can't reproduce it, then you'll have to test our changes for us until it's fixed -- and then we can't add test cases, either.

In this case, we especially need to reproduce this with curl -v commands (and not web browsers), and a minimal directory structure to understand the problem fully.

I've attached a template below that will help make this easier and faster! This will require some effort on your part -- please understand that we will be dedicating time to fix the bug you are reporting if you can just help us understand it and reproduce it easily.

This template will ask for some information you've already provided; that's OK, just fill it out the best you can. 👍 I've also included some helpful tips below the template. Feel free to let me know if you have any questions!

Thank you again for your report, we look forward to resolving it!

Template

## 1. Environment

### 1a. Operating system and version

```
paste here
```


### 1b. Caddy version (run `caddy version` or paste commit SHA)

This should be the latest version of Caddy:

```
paste here
```


## 2. Description

### 2a. What happens (briefly explain what is wrong)




### 2b. Why it's a bug (if it's not obvious)




### 2c. Log output

```
paste terminal output or logs here
```



### 2d. Workaround(s)




### 2e. Relevant links




## 3. Tutorial (minimal steps to reproduce the bug)




Instructions -- please heed otherwise we cannot help you (help us help you!)

  1. Environment: Please fill out your OS and Caddy versions, even if you don't think they are relevant. (They are always relevant.) If you built Caddy from source, provide the commit SHA and specify your exact Go version.

  2. Description: Describe at a high level what the bug is. What happens? Why is it a bug? Not all bugs are obvious, so convince readers that it's actually a bug.

    • 2c) Log output: Paste terminal output and/or complete logs in a code block. DO NOT REDACT INFORMATION except for credentials. Please enable debug and access logs.
    • 2d) Workaround: What are you doing to work around the problem in the meantime? This can help others who encounter the same problem, until we implement a fix.
    • 2e) Relevant links: Please link to any related issues, pull requests, docs, and/or discussion. This can add crucial context to your report.
  3. Tutorial: What are the minimum required specific steps someone needs to take in order to experience the same bug? Your goal here is to make sure that anyone else can have the same experience with the bug as you do. You are writing a tutorial, so make sure to carry it out yourself before posting it. Please:

    • Start with an empty config. Add only the lines/parameters that are absolutely required to reproduce the bug.
    • Do not run Caddy inside containers.
    • Run Caddy manually in your terminal; do not use systemd or other init systems.
    • If making HTTP requests, avoid web browsers. Use a simpler HTTP client instead, like curl.
    • Do not redact any information from your config (except credentials). Domain names are public knowledge and often necessary for quick resolution of an issue!
    • Note that ignoring this advice may result in delays, or even in your issue being closed. 😞 Only actionable issues are kept open, and if there is not enough information or clarity to reproduce the bug, then the report is not actionable.

Example of a tutorial:

Create a config file:
{ ... }

Open terminal and run Caddy:

$ caddy ...

Make an HTTP request:

$ curl ...

Notice that the result is ___ but it should be ___.

@mholt mholt added the needs info 📭 Requires more information label Jun 28, 2024
@odama626
Copy link
Author

I wasn't able to reproduce it with a curl command. I think it has something to do with some kind of caching and I'm not sure how to achieve that in curl.

I was able to create a minimal setup that uses the browser. I created a basic project with a docker compose script below and you can reproduce it by navigating to http://localhost/test/me and refreshing the page with the keyboard shortcut

I tested in in chrome and firefox along with in chrome and firefox in incognito mode

caddy-reproduction.zip

@francislavoie
Copy link
Member

You should use try_files for this, not handle_errors. Wrong tool for this job. See the docs: https://caddyserver.com/docs/caddyfile/patterns#single-page-apps-spas

@odama626
Copy link
Author

I'll have to give it a try tomorrow. I do think this is a bug though since it works as expected during navigation or a hard refresh but returns an empty body during a soft refresh

@francislavoie
Copy link
Member

If you could add the debug global option and the log directive in your site block (for access logs), then show us your logs, that would help us understand.

@mholt
Copy link
Member

mholt commented Jun 29, 2024

That sounds like a bug in the SPA 🤔 especially if it can't be reproduced with curl. Having to use the browser to reproduce a bug is almost always indicative of a bug in the web app or even the browser (we've seen both).

@odama626
Copy link
Author

odama626 commented Jun 29, 2024

@mholt It's not the spa, I reproduced it above with a bare html file. also, if you remove the status 200 from the caddyfile it consistently returns the correct body.

<!DOCTYPE html>
<html>
  <head>
    <link rel="shortcut icon" type="image/ico" href="/favicon.ico" />
  </head>
  <body>
    hi
  </body>
</html>

@francislavoie

Attaching to caddy-1
caddy-1  | {"level":"info","ts":1719681921.4997637,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"}
caddy-1  | {"level":"warn","ts":1719681921.503575,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":18}
caddy-1  | {"level":"info","ts":1719681921.5046237,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]}
caddy-1  | {"level":"warn","ts":1719681921.5048866,"logger":"http.auto_https","msg":"automatic HTTPS is completely disabled for server","server_name":"srv0"}
caddy-1  | {"level":"debug","ts":1719681921.5049403,"logger":"http.auto_https","msg":"adjusted config","tls":{"automation":{"policies":[{}]}},"http":{"servers":{"srv0":{"listen":[":80"],"routes":[{"handle":[{"handler":"vars","root":"/files"},{"handler":"file_server","hide":["/etc/caddy/Caddyfile"]}]}],"errors":{"routes":[{"group":"group0","handle":[{"handler":"rewrite","uri":"/"}]},{"handle":[{"handler":"file_server","hide":["/etc/caddy/Caddyfile"],"status_code":200}]}]},"automatic_https":{"disable":true},"logs":{}}}}}
caddy-1  | {"level":"debug","ts":1719681921.5053997,"logger":"http","msg":"starting server loop","address":"[::]:80","tls":false,"http3":false}
caddy-1  | {"level":"info","ts":1719681921.5056481,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
caddy-1  | {"level":"info","ts":1719681921.5056968,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc0001d6880"}
caddy-1  | {"level":"info","ts":1719681921.5059073,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
caddy-1  | {"level":"info","ts":1719681921.5059547,"msg":"serving initial configuration"}
caddy-1  | {"level":"warn","ts":1719681921.5076447,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/data/caddy","instance":"06b15745-6be8-4705-b5dd-6909966c476d","try_again":1719768321.5076432,"try_again_in":86399.999999605}
caddy-1  | {"level":"info","ts":1719681921.507745,"logger":"tls","msg":"finished cleaning storage units"}
caddy-1  | {"level":"debug","ts":1719681935.3437395,"logger":"http.handlers.file_server","msg":"sanitized path join","site_root":"/files","request_path":"/asdf/fds","result":"/files/asdf/fds"}
caddy-1  | {"level":"debug","ts":1719681935.3440337,"logger":"http.handlers.rewrite","msg":"rewrote request","request":{"remote_ip":"192.168.65.1","remote_port":"33178","client_ip":"192.168.65.1","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/asdf/fds","headers":{"Sec-Ch-Ua":["\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\""],"Sec-Fetch-Mode":["navigate"],"If-None-Match":["\"sftwhh42\""],"Connection":["keep-alive"],"Sec-Ch-Ua-Platform":["\"macOS\""],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Sec-Fetch-Site":["none"],"Cache-Control":["max-age=0"],"Sec-Ch-Ua-Mobile":["?0"],"Dnt":["1"],"Accept-Language":["en-US,en;q=0.9"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-User":["?1"],"Sec-Fetch-Dest":["document"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"method":"GET","uri":"/"}
caddy-1  | {"level":"debug","ts":1719681935.3440845,"logger":"http.handlers.file_server","msg":"sanitized path join","site_root":"/files","request_path":"/","result":"/files"}
caddy-1  | {"level":"debug","ts":1719681935.345007,"logger":"http.handlers.file_server","msg":"located index file","filename":"/files/index.html"}
caddy-1  | {"level":"debug","ts":1719681935.3450918,"logger":"http.handlers.file_server","msg":"opening file","filename":"/files/index.html"}
caddy-1  | {"level":"debug","ts":1719681935.3506725,"logger":"http.log.error","msg":"{id=b8m4t24ad} fileserver.(*FileServer).notFound (staticfiles.go:629): HTTP 404","request":{"remote_ip":"192.168.65.1","remote_port":"33178","client_ip":"192.168.65.1","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/asdf/fds","headers":{"Connection":["keep-alive"],"Sec-Ch-Ua-Platform":["\"macOS\""],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Cache-Control":["max-age=0"],"Sec-Ch-Ua-Mobile":["?0"],"Dnt":["1"],"Sec-Fetch-Site":["none"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-User":["?1"],"Sec-Fetch-Dest":["document"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-US,en;q=0.9"],"Sec-Ch-Ua":["\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\""],"Sec-Fetch-Mode":["navigate"],"If-None-Match":["\"sftwhh42\""]}},"duration":0.000301506,"status":404,"err_id":"b8m4t24ad","err_trace":"fileserver.(*FileServer).notFound (staticfiles.go:629)"}
caddy-1  | {"level":"info","ts":1719681935.3507602,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"192.168.65.1","remote_port":"33178","client_ip":"192.168.65.1","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/asdf/fds","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Dnt":["1"],"Sec-Fetch-Site":["none"],"Cache-Control":["max-age=0"],"Sec-Fetch-Dest":["document"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-US,en;q=0.9"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-User":["?1"],"If-None-Match":["\"sftwhh42\""],"Sec-Ch-Ua":["\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\""],"Sec-Fetch-Mode":["navigate"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Connection":["keep-alive"],"Sec-Ch-Ua-Platform":["\"macOS\""]}},"bytes_read":0,"user_id":"","duration":0.000301506,"size":0,"status":200,"resp_headers":{"Server":["Caddy"],"Etag":["\"sftwhh42\""]}}

@odama626
Copy link
Author

odama626 commented Jun 29, 2024

@mholt I just remembered that you can copy a network request as a curl command in chrome, and I was able to reproduce it with curl. you may have to run the curl command twice because the first request works, it's just subsequent ones

curl -v 'http://localhost/asdf/fds' \ 
  -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \
  -H 'Accept-Language: en-US,en;q=0.9' \
  -H 'Cache-Control: max-age=0' \
  -H 'Connection: keep-alive' \
  -H 'DNT: 1' \
  -H 'If-None-Match: "sftwhh42"' \
  -H 'Sec-Fetch-Dest: document' \
  -H 'Sec-Fetch-Mode: navigate' \
  -H 'Sec-Fetch-Site: none' \
  -H 'Sec-Fetch-User: ?1' \
  -H 'Upgrade-Insecure-Requests: 1' \
  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' \
  -H 'sec-ch-ua: "Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"'

@mohammed90
Copy link
Member

-H 'If-None-Match: "sftwhh42"'

My gut tells me it has to do with the If-None-Match. I'm trying to remember the nuance of sending the header to Caddy file_server.

@mohammed90
Copy link
Member

mohammed90 commented Jun 29, 2024

Alright, I was right. It has to do with the etag and If-None-Match header.

When using file_server, Caddy calculates the etag and sends it across. Web browsers store the etag and send it on repeat requests for the same resource as the value for If-None-Match header. Caddy receives this request with the If-None-Match header, so Caddy finds the subject resource and calculates its etag hash. Caddy finds the hash matches the etag, and the browser did send the If-None-Match header, indicating it knows how to manage caches, so Caddy responds with HTTP 304 without body, conforming to the respective HTTP specs. BUT you told Caddy "don't give me 304, return 200 instead", so Caddy overrides the status code but the body is is still empty.

Ultimately, this is not a bug in Caddy. It's a user configuration error. The proper configuration is to follow @francislavoie suggestion and the doc page he linked to.

@odama626
Copy link
Author

if I remove the status 200 it consistently returns a 404, but I'm guessing that the browser don't pass cache headers on 404s?
This was really confusing and I spent a lot of time trying to debug what was going on with it. maybe it would be a good idea to log a warning in debug mode when a status is changed from a 304 to something else?

@odama626
Copy link
Author

so I switched to try_files, and it is returning index.html for the favicon. I looked through the docs and wasn't able to find where {path} comes from or if there are other similar ones, because I think what I want would be closer to try_files {path} {filename}

:80 {
	root * /files
	log

	try_files {path} /index.html
	file_server

}

@francislavoie
Copy link
Member

francislavoie commented Jun 30, 2024

so I switched to try_files, and it is returning index.html for the favicon.

Yeah, so you need to add a favicon.ico file to your webroot, or set up your index.html to have the necessary meta tags for the favicon so the browser doesn't try to request the default one.

I looked through the docs and wasn't able to find where {path} comes from

It's a placeholder: https://caddyserver.com/docs/caddyfile/concepts#placeholders, it's the current request path.

because I think what I want would be closer to try_files {path} {filename}

No, that's wrong. That wouldn't do anything useful.

@odama626
Copy link
Author

odama626 commented Jun 30, 2024

Yeah, so you need to add a favicon.ico file to your webroot, or set up your index.html to have the necessary meta tags for the favicon so the browser doesn't try to request the default one.

I mentioned this, because I'd like caddy to return a 404 since there is not a favicon.ico

No, that's wrong. That wouldn't do anything useful.

try_files {path} /index.html will return a 200 with index.html for an image that should be a 404,

I was trying to achieve:

/random/path -> random/path/index.png or /index.html or 404
/random/path/image.png -> /random/path/image.png or /image.png or 404
/random/path/index.html -> /random/path/index.html or /index.hml or 404

I mentioned it above but it may have gotten missed:
I think it would be a good idea to log a warning in debug mode when a status is changed from a 304 to something since that is probably not what someone wants to do normally

@mohammed90
Copy link
Member

I think it would be a good idea to log a warning in debug mode when a status is changed from a 304 to something since that is probably not what someone wants to do normally

It was changed from 304 to 200 because you explicitly configured Caddy to do that. It'd be very strange to WARN for something the user explicitly configure here:

file_server {
status 200
}

For your use case, I'm suspecting we're dealing with the XY Problem because in your original post you mentioned favicons. For favicon, the browsers actually request /favicon.png (on non-Windows) and favicon.ico on Windows. It doesn't request the favicon by requesting /. The browser requests example.com/favicon.png (unless overridden by the link HTML tag) when visiting any page under the domain.

Regarding try_files, the 404 fallback pattern is described in the docs on the try_files documentation page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs info 📭 Requires more information
Projects
None yet
Development

No branches or pull requests

4 participants