Go 制作的生产服务器出现恐慌
来源:stackoverflow
时间:2024-03-30 20:48:31 482浏览 收藏
IT行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰。所以我们需要踏踏实实的不断学习,精进自己的技术,尤其是初学者。今天golang学习网给大家整理了《Go 制作的生产服务器出现恐慌》,聊聊,我们一起来看看吧!
我感到恐慌,我试图理解它,但我不太确定为什么会恐慌。错误看起来像这样:
main.httpsnonwwwredirect.func1(0x9a5a20, 0xc42015c2a0, 0xc420441400) /srv/www/go/src/srorapp.no/handler.go:119 +0x1ef net/http.handlerfunc.servehttp(0xc4200c5f20, 0x9a5a20, 0xc42015c2a0, 0xc420441400) /usr/local/go/src/net/http/server.go:1918 +0x44 net/http.serverhandler.servehttp(0xc4200696c0, 0x9a5a20, 0xc42015c2a0, 0xc420441400) /usr/local/go/src/net/http/server.go:2619 +0xb4 net/http.(*conn).serve(0xc42006d180, 0x9a5fa0, 0xc42031e840) /usr/local/go/src/net/http/server.go:1801 +0x71d created by net/http.(*server).serve /usr/local/go/src/net/http/server.go:2720 +0x288
看起来它是由一个名为 httpsnonwwwredirect 的函数触发的。这是我创建的http中间件:
// httpsnonwwwredirect redirects http requests to https non www. func httpsnonwwwredirect(next http.handler) http.handler { return http.handlerfunc(func(w http.responsewriter, r *http.request) { if r.tls != nil { // if already using https then continue. next.servehttp(w, r) return } u := *r.url u.scheme = "https" if r.host[:3] != "www" { u.host = r.host http.redirect(w, r, u.string(), http.statusmovedpermanently) return } u.host = r.host[4:] http.redirect(w, r, u.string(), http.statusmovedpermanently) }) }
此功能与以下功能一起使用:
// nonwwwredirect redirects www requests to non www. func nonwwwredirect(next http.handler) http.handler { return http.handlerfunc(func(w http.responsewriter, r *http.request) { if r.host[:3] != "www" { // if already non www, then continue. next.servehttp(w, r) return } u := *r.url u.host = r.host[4:] u.scheme = utils.scheme(r) http.redirect(w, r, u.string(), http.statusmovedpermanently) }) }
然后我有两个函数从端口 80 和 443 提供服务。
func servehttp(h http.handler) { log.fatal(http.listenandserve(":80", h)) } func servehttps(h http.handler) { log.fatal(http.listenandservetls(":443", cfg.tlscertfile, cfg.tlskeyfile, h)) }
我对 julienschmidt httprouter 做了一个包装,以使事情变得更方便:
// https://gist.github.com/nmerouze/5ed810218c661b40f5c4 type router struct { r *httprouter.router } func newrouter() *router { return &router{r: httprouter.new()} }
主要是我有这样的东西:
func main() { router := newrouter() recover := alice.new(recoverhandler) // .... redirect := alice.new(httpsnonwwwredirect, nonwwwredirect) handler := redirect.then(router.r) go servehttp(handler) servehttps(handler) }
这是handler.go的内容
package main import ( "context" "log" "net/http" "os" "path/filepath" "strings" "github.com/julienschmidt/httprouter" cfg "srorapp.no/config" "srorapp.no/user" "srorapp.no/utils" ) // https://gist.github.com/nmerouze/5ed810218c661b40f5c4 type router struct { r *httprouter.Router } func newRouter() *router { return &router{r: httprouter.New()} } var paramsKey utils.CtxKey = "params" func paramsHandler(h http.Handler) httprouter.Handle { return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { ctx := context.WithValue(r.Context(), paramsKey, ps) h.ServeHTTP(w, r.WithContext(ctx)) }) } // https://www.calhoun.io/pitfalls-of-context-values-and-how-to-avoid-or-mitigate-them/ func params(r *http.Request) httprouter.Params { // https://blog.golang.org/context // "Value allows a Context to carry request-scoped data. // That data must be safe for simultaneous use by multiple goroutines." // http://stackoverflow.com/questions/42893937/do-i-need-mutex-read-lock-when-retrieving-slice-values-with-context-in-go?noredirect=1#comment72889988_42893937 // Do not need a mutex here since I will access it in a simple way and not concurrently. value := r.Context().Value(paramsKey) if ps, ok := value.(httprouter.Params); ok { return ps } return httprouter.Params{} } func (r *router) GET(path string, handler http.Handler) { r.r.GET(path, paramsHandler(handler)) } func (r *router) POST(path string, handler http.Handler) { r.r.POST(path, paramsHandler(handler)) } // ------------------------------------------------------------------------------------------- type errorHandlerFunc func(http.ResponseWriter, *http.Request) error // http://stackoverflow.com/questions/42871194/how-can-i-combine-go-middleware-pattern-with-error-returning-request-handlers/42876307#42876307 func errorHandler(h errorHandlerFunc) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Set Content-Type to plain text when sending http.Error. w.Header().Set("Content-Type", "application/json") if err := h(w, r); err != nil { // w.Header().Set("Content-Type", "text/html; charset=utf-8") // http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) log.Println(err) } }) } // ------------------------------------------------------------------------------------------- // https://github.com/labstack/echo/blob/master/middleware/redirect.go // http://stackoverflow.com/questions/42916952/do-you-have-to-return-after-a-http-redirect-if-you-want-the-code-after-to-stop-e // https://play.golang.org/p/uk0S1hCPhu // HTTPSRedirect redirects HTTP to HTTPS. func HTTPSRedirect(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.TLS != nil { // If already using HTTPS then continue. next.ServeHTTP(w, r) return } http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently) }) } // HTTPSWWWRedirect redirects http requests to https www. func HTTPSWWWRedirect(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.TLS != nil { // If already using HTTPS then continue. next.ServeHTTP(w, r) return } u := *r.URL // Dereference *url.URL to make a copy. u.Scheme = "https" u.Host = "www." + strings.TrimPrefix(r.Host, "www.") http.Redirect(w, r, u.String(), http.StatusMovedPermanently) }) } // HTTPSNonWWWRedirect redirects http requests to https non www. func HTTPSNonWWWRedirect(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.TLS != nil { // If already using HTTPS then continue. next.ServeHTTP(w, r) return } u := *r.URL u.Scheme = "https" if r.Host[:3] != "www" { u.Host = r.Host http.Redirect(w, r, u.String(), http.StatusMovedPermanently) return } u.Host = r.Host[4:] http.Redirect(w, r, u.String(), http.StatusMovedPermanently) }) } // WWWRedirect redirects non www requests to www. func WWWRedirect(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Host[:3] == "www" { // If already www, then continue. next.ServeHTTP(w, r) return } u := *r.URL u.Host = "www." + r.Host u.Scheme = utils.Scheme(r) http.Redirect(w, r, u.String(), http.StatusMovedPermanently) }) } // NonWWWRedirect redirects www requests to non www. func NonWWWRedirect(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Host[:3] != "www" { // If already non www, then continue. next.ServeHTTP(w, r) return } u := *r.URL u.Host = r.Host[4:] u.Scheme = utils.Scheme(r) http.Redirect(w, r, u.String(), http.StatusMovedPermanently) }) } func canServeGzip(r *http.Request) bool { if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { // If for some weird reason client does not understand gzip. return false } path := filepath.FromSlash(filepath.Join(cfg.PublicHTML, r.URL.Path)) if _, err := os.Stat(path); os.IsNotExist(err) { // If file or folder does not exists. return false } fileExt := filepath.Ext(r.URL.Path) if !utils.StringInSlice(cfg.GzipFileExt, fileExt) { // This file should not be served as gzipped content. return false } // Only serve gzipped file if it already exists. if _, err := os.Stat(path + ".gz"); os.IsNotExist(err) { // TODO: Create the gzipped file. // http://stackoverflow.com/questions/16890648/how-can-i-use-golangs-compress-gzip-package-to-gzip-a-file return false } return true } func gzipHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer next.ServeHTTP(w, r) if !canServeGzip(r) { // fmt.Println("as original", r.URL.Path) return } w.Header().Add("Content-Encoding", "gzip") w.Header().Add("Content-Type", contentType(filepath.Ext(r.URL.Path))) r.URL.Path = r.URL.Path + ".gz" // fmt.Println("as gzip", r.URL.Path) }) } func recoverHandler(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { log.Printf("panic: %+v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } }() // time.Sleep(time.Millisecond * 500) next.ServeHTTP(w, r) } return http.HandlerFunc(fn) } var userIDKey utils.CtxKey = "userID" func authHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { userID, err := user.IsLoggedIn(r) if err != nil { log.Printf("main authHandler() %v", err) http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } ctx := context.WithValue(r.Context(), userIDKey, userID) next.ServeHTTP(w, r.WithContext(ctx)) }) } func adminHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // time.Sleep(time.Millisecond * 600) isLoggedInAsAdmin, err := user.IsLoggedInAsAdmin(r) if err != nil || !isLoggedInAsAdmin { if !isLoggedInAsAdmin { log.Printf("main adminHandler() User is not logged in as admin %v", err) } else { log.Printf("main adminHandler() %v", err) } http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } ctx := context.WithValue(r.Context(), userIDKey, 1) next.ServeHTTP(w, r.WithContext(ctx)) }) } // ------------------------------------------------------------------------------------------- func serveDevHTTP(h http.Handler) { log.Fatal(http.ListenAndServe(":8080", h)) // log.Fatal(http.ListenAndServe(":80", h)) } func serveHTTP(h http.Handler) { log.Fatal(http.ListenAndServe(":80", h)) } func serveHTTPS(h http.Handler) { log.Fatal(http.ListenAndServeTLS(":443", cfg.TLSCertFile, cfg.TLSKeyFile, h)) }
我不知道如何调试这种恐慌。
解决方案
1。问题
handler.go:119
包含 if
语句。您尝试从 host
标头获取前三个字符。
if r.host[:3] != "www" { u.host = r.host http.redirect(w, r, u.string(), http.statusmovedpermanently) return }
通常情况下,r.host
会存储请求url信息。 除非 host
标头在请求中显式更改。来自net/http package doc:
type request struct { // ... // for server requests host specifies the host on which the url // is sought. per rfc 7230, section 5.4, this is either the value // of the "host" header or the host name given in the url itself. // it may be of the form "host:port". for international domain // names, host may be in punycode or unicode form. use // golang.org/x/net/idna to convert it to either format if // needed. // to prevent dns rebinding attacks, server handlers should // validate that the host header has a value for which the // handler considers itself authoritative. the included // servemux supports patterns registered to particular host // names and thus protects its registered handlers. // // for client requests host optionally overrides the host // header to send. if empty, the request.write method uses // the value of url.host. host may contain an international // domain name. host string // ... }
所以发生恐慌是因为 r.host
填充了空字符串,或者某些字符数低于 3 的字符串。
2。测试
我使用 go 创建了非常简单的 web 应用程序,它打印 r.host[:3]
的值。我使用curl对其进行了测试,并将host
标头设置为空。
curl --verbose --header 'host: ' http://localhost:8080
它会引发恐慌,我很确定这与您得到的恐慌错误相同。
2018/12/07 08:11:54 http: panic serving 127.0.0.1:50889: runtime error: slice bounds out of range goroutine 37 [running]: net/http.(*conn).serve.func1(0xc0001380a0) /usr/local/opt/go/libexec/src/net/http/server.go:1746 +0xd0 panic(0x125c0c0, 0x14964d0) /usr/local/opt/go/libexec/src/runtime/panic.go:513 +0x1b9 main.main.func1(0x12efa80, 0xc00014c2a0, 0xc000162300) /users/novalagung/desktop/test.go:11 +0x13d net/http.handlerfunc.servehttp(0x12bcd98, 0x12efa80, 0xc00014c2a0, 0xc000162300) /usr/local/opt/go/libexec/src/net/http/server.go:1964 +0x44 net/http.(*servemux).servehttp(0x14a17a0, 0x12efa80, 0xc00014c2a0, 0xc000162300) /usr/local/opt/go/libexec/src/net/http/server.go:2361 +0x127 net/http.serverhandler.servehttp(0xc000093110, 0x12efa80, 0xc00014c2a0, 0xc000162300) /usr/local/opt/go/libexec/src/net/http/server.go:2741 +0xab net/http.(*conn).serve(0xc0001380a0, 0x12efc80, 0xc000146100) /usr/local/opt/go/libexec/src/net/http/server.go:1847 +0x646 created by net/http.(*server).serve /usr/local/opt/go/libexec/src/net/http/server.go:2851 +0x2f5
3。解决方案
解决方案很简单,只需确保 r.host
值不是空字符串且长度大于 2 即可。最好使用 strings.hasprefix()
来完成此操作。
if strings.HasPrefix(r.Host, "www") { u.Host = r.Host http.Redirect(w, r, u.String(), http.StatusMovedPermanently) return }
到这里,我们也就讲完了《Go 制作的生产服务器出现恐慌》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于的知识点!
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
139 收藏
-
204 收藏
-
325 收藏
-
477 收藏
-
486 收藏
-
439 收藏
-
357 收藏
-
352 收藏
-
101 收藏
-
440 收藏
-
212 收藏
-
143 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习