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

Implement custom noroute html response #398

Merged
merged 8 commits into from
Dec 16, 2017

Conversation

tino
Copy link
Contributor

@tino tino commented Dec 3, 2017

PR for #56

  • Sign CLA
  • Implement functionality for consul backend
  • Implement functionality for file backend
  • Write tests
  • Add default no route HTML? (Like all other servers do, but then styled a bit nicer :)

My first contribution to a Go project, so let me know what can be done better!

If tests are need, for what should I write them?

Closes #56

@tino
Copy link
Contributor Author

tino commented Dec 3, 2017

BTW, I added https://github.com/fabiolb/fabio/wiki/Contributing#developing, as it took me quite a while to find out how this works with Go. Might need some rewriting?

@magiconair
Copy link
Contributor

Hi Tino, thanks for the contribution. I will have a look right away. Also, thanks for adding the Developing section to the Wiki. Your description is spot on!

Copy link
Contributor

@magiconair magiconair left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, this looks OK. I think NoRouteHTMLPath is a bit too clumsy for a name but can't come up with something better right now.

The final version would need to have an integration test in the proxy/http_integration_test.go file.

However, I do think that we could generalize the approach a bit further. If the code would watch a path in consul then we could have multiple custom files, e.g.

/fabio/page/noroute.html
/fabio/page/404.html
/fabio/page/503.html
...

store would then contain a map[string]string where the key is the code and the value is the page.

If there is a page for that code or for noroute then fabio should return that.

However, this becomes more difficult in the case of 404 and 503 from the upstream service since the response has already been copied to the client. Fabio would have to sniff the status code from the response stream to inject its own response. The httputil.ReverseProxy doesn't provide proper hooks for that AFAIR but I would need to look. If not, then this would certainly be a larger change.

What we could do nevertheless is to prepare the configuration for that so that when the feature gets implemented users don't have to change things.

"sync/atomic"
)

// HTML Wrapper struct so we can store the html string in an atomic.Value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need this since you can store a string in an atomic.Value directly.

value string
}

// html stores the no route html string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually add a comment after the value to indicate the type since it cannot be changed once it is set, .e.g.

var store atomic.Value // string

// GetHTML returns the HTML for not found routes. The function is safe to be
// called from multiple goroutines.
func GetHTML() string {
return store.Load().(HTML).value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This then becomes

return store.Load().(string)

return store.Load().(HTML).value
}

// hmu guards the atomic writes in SetHTML.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An atomic.Value is already synchronized. Therefore, you don't need a mutex here.

// WatchNoRouteHTML implementation that reads the noroute html from a
// noroute.html file if it exists
func (b *be) WatchNoRouteHTML() chan string {
data, err := ioutil.ReadFile("noroute.html")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noroute.html is a magic value and would need to be configurable.

@@ -0,0 +1,44 @@
package route
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably create a separate package for this since neither route nor proxy is a good place to keep this.

@tino
Copy link
Contributor Author

tino commented Dec 6, 2017

On the mutex part, I copied this from the route/table.go. Why is it needed there and not here?

I held off on multiple custom error pages. In my view if you set an error code in the upstream service, you should also provide the content you want to serve, and fabio should not overwrite it.

This page is just for when the complete service could not be found, and is in sync with the Proxy.NoRouteStatus value, hence the – bit clumsy, but clearer then other things I could come up with – name.

@magiconair
Copy link
Contributor

Re mutex: I guess you're referring to this:

https://github.com/fabiolb/fabio/blob/master/route/table.go#L47-L56

The reason a mutex is required here is that it changes two things: the table and the metrics registry.

I'll have a look at the rest today.

@tino
Copy link
Contributor Author

tino commented Dec 11, 2017

Ah okay. I missed that.

@tino
Copy link
Contributor Author

tino commented Dec 14, 2017

I decided not to add a default HTML message. Although I would like not to serve a browsers default 503/404 by default to humans, it took me too much effort to cleanly implement with templates how I had it in mind.

Is this mergeable as-is, or do you need some more changes? If so, we can start to remove our nginx instances that are in front of fabio atm ☺️

@magiconair
Copy link
Contributor

I’ll have a quick look today

@magiconair
Copy link
Contributor

magiconair commented Dec 15, 2017

This looks fine except that the name is still bugging me because it is too specific. How about we call it TemplatePath and keep a generic list of templates? Then for the no route case we use the noroute.html template.

I can do this or you can pick this up. I'd like to get this merged as well.

@magiconair
Copy link
Contributor

What I suggested is a larger refactor. I'll pick that up myself later. I'm going to merge this and amend the code with some small cleanup.

Thanks for this!

@magiconair magiconair merged commit b84bb2a into fabiolb:master Dec 16, 2017
@magiconair magiconair added this to the 1.5.5 milestone Dec 21, 2017
@traceypooh
Copy link

i'm... at an utter loss here...

i finally got consul k/v setup with html, but no matter what i do, it comes out as plaintext, w/

  Content-Type: text/plain; charset=utf-8

type headers regardless of what I try.

is there some magic "front matter"-like top part of the k/v string i'm supposed to use?
(I haven't been able to get any kind of configuration w/ files, like anything swirling around flailage like this to work on fabio.properties either. so... so far, consul k/v seems my only hope!)

registry.file.noroutehtmlpath = /etc/fabio/404.html
registry.static.noroutehtmlpath = "<h1>not found</h1>"
registry.static.noroutehtml = "<h1>not found</h1>"
registry.static.noroutehtmlpath = /etc/fabio/404.html

any help vastly appreciated as we're trying to make a bigger move to nomad and fabio...

@tristanmorgan
Copy link

For me I have an HTML snippet in the consul KV and Fabio returns appropriate headers.

$ curl -vv https://consul-esm.service.consul
.....
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 404 
< content-type: text/html; charset=utf-8
< content-length: 189
< date: Thu, 13 Jan 2022 03:19:01 GMT
< 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<a href="https://fabio.service.consul/">See Fabio</a>.
* Connection #0 to host consul-esm.service.consul left intact
</body></html>

$ consul kv get fabio/noroute.html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<a href="https://fabio.service.consul/">See Fabio</a>.
</body></html>

@traceypooh
Copy link

thanks @tristanmorgan !

ooh, that totally worked for me.
(i wonder if there's some kind of autodetection for starting kv string with <!DOCTYPE.. -- but that works while yesterday lots of attempts w/o that lead line was not working for me).

🙏

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 this pull request may close these issues.

Support custom 404/503 error pages
4 participants