diff --git a/server/consts.go b/server/consts.go index 8da677c..bae13df 100644 --- a/server/consts.go +++ b/server/consts.go @@ -25,6 +25,8 @@ const ( falseUaFieldStr string = "No" dfltIndent string = " " httpRealHdr string = "X-ClientInfo-RealIP" // TODO: advise https://nginx.org/en/docs/http/ngx_http_realip_module.html NGINX module? Allow config for user-specified header? + httpCIDHdr string = "Request-Id" + urlParamXCheck string = "src_req_id" ) var ( @@ -36,7 +38,7 @@ var ( template.FuncMap{ "getTitle": getTitle, "getIpver": getIpver, - "safeUrl": safeUrl, + "safeUrl": safeUrl, }, ).ParseFS(tplDir, "tpl/*.tpl"), ) diff --git a/server/funcs_page.go b/server/funcs_page.go index f671ca4..8a3c208 100644 --- a/server/funcs_page.go +++ b/server/funcs_page.go @@ -2,20 +2,30 @@ package server import ( `fmt` + `net/url` ) func (p *Page) AltURL() (altUrl string) { + var u *url.URL + if !p.HasAltURL() { return } if p.Info.IP.Is4() { - altUrl = p.srv.v6Url.String() + u = p.srv.v6Url } else if p.Info.IP.Is6() { - altUrl = p.srv.v4Url.String() + u = p.srv.v4Url + } else { + // lol how did you get here + return } + u.Query().Add(urlParamXCheck, p.ReqUUID.String()) + + altUrl = u.String() + return } diff --git a/server/funcs_server.go b/server/funcs_server.go index 7e2d791..399ac79 100644 --- a/server/funcs_server.go +++ b/server/funcs_server.go @@ -19,6 +19,7 @@ import ( sysd `github.com/coreos/go-systemd/daemon` `github.com/davecgh/go-spew/spew` `github.com/goccy/go-yaml` + `github.com/google/uuid` `r00t2.io/clientinfo/version` `r00t2.io/goutils/multierr` ) @@ -261,6 +262,7 @@ func (s *Server) Reload() (err error) { } func (s *Server) explicit404(resp http.ResponseWriter, req *http.Request) { + s.log.Debug("server.Server.explicit404: '%s' requested '%s %s'", req.RemoteAddr, req.Method, req.URL.String()) resp.WriteHeader(http.StatusNotFound) } @@ -278,10 +280,12 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { var parsedFmts []*parsedMIME var renderer outerRenderer var includeFmt string + var reqId uuid.UUID = uuid.New() var params url.Values = make(url.Values) var outerFmt string = mediaJSON - s.log.Debug("server.Server.handleDefault: Handling request:\n%s", spew.Sdump(req)) + s.log.Info("server.Server.handleDefault: '%s' (%s): '%s %s'", reqId.String(), req.RemoteAddr, req.Method, req.URL.String()) + s.log.Debug("server.Server.handleDefault: Handling request for '%s':\n%s", reqId.String(), spew.Sdump(req)) origin = req.Header.Get("Origin") if s.corsOrigins != nil && len(s.corsOrigins) != 0 && origin != "" { if _, ok = s.corsOrigins[origin]; ok { @@ -291,6 +295,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { } resp.Header().Set("ClientInfo-Version", version.Ver.Short()) + resp.Header().Set(httpCIDHdr, reqId.String()) page = &Page{ Info: &R00tInfo{ @@ -305,6 +310,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { RawFmt: nil, Indent: "", DoIndent: false, + ReqUUID: reqId, } // First the client info. @@ -316,7 +322,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { } if remAddrPort != "" { if nAP, err = netip.ParseAddrPort(remAddrPort); err != nil { - s.log.Warning("server.Server.handleDefault: Failed to parse remote address '%s': %v", remAddrPort, err) + s.log.Warning("server.Server.handleDefault: Failed to parse remote address '%s' for '%s': %v", remAddrPort, reqId.String(), err) // Don't return an error in case we're doing weird things like direct socket clients. /* http.Error(resp, "ERROR: Failed to parse client address", http.StatusInternalServerError) @@ -329,13 +335,16 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { } if req.URL != nil { params = req.URL.Query() + if params.Has(urlParamXCheck) { + s.log.Info("server.Server.handleDefault: Cross-stack check: '%s' => '%s'", params.Get(urlParamXCheck), reqId.String()) + } } uas = req.Header.Values("User-Agent") if uas != nil && len(uas) > 0 { page.Info.Client = make([]*R00tClient, 0, len(uas)) for _, ua := range uas { if parsedUA, err = NewClient(ua); err != nil { - s.log.Err("server.Server.handleDefault: Failed to create client for '%s': %v", ua, err) + s.log.Err("server.Server.handleDefault: Failed to create client for UA '%s' via request '%s': %v", ua, reqId.String(), err) http.Error(resp, fmt.Sprintf("ERROR: Failed to parse 'User-Agent' '%s'", ua), http.StatusInternalServerError) return } @@ -363,7 +372,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { reqdMimes = req.Header.Values("Accept") if reqdMimes != nil && len(reqdMimes) > 0 { if parsedFmts, err = parseAccept(strings.Join(reqdMimes, ",")); err != nil { - s.log.Err("server.Server.handleDefault: Failed to parse Accept header '%#v' for '%s': %v", reqdMimes, remAddrPort, err) + s.log.Err("server.Server.handleDefault: Failed to parse Accept header '%#v' for '%s' ('%s'): %v", reqdMimes, remAddrPort, reqId.String(), err) resp.Header()["Accept"] = okAcceptMime http.Error( resp, @@ -374,12 +383,12 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { } if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil { if errors.Is(err, ErrUnsupportedMIME) { - s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v'.", remAddrPort, reqdMimes) + s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v' for '%s'.", remAddrPort, reqdMimes, reqId.String()) resp.Header()["Accept"] = okAcceptMime http.Error(resp, "ERROR: No supported MIME type specified via request 'Accept'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable) return } else { - s.log.Err("server.Server.handleDefault: Received unknown error choosing from Accept header for '%s': %v", remAddrPort, err) + s.log.Err("server.Server.handleDefault: Received unknown error choosing from Accept header for '%s' from '%s': %v", remAddrPort, reqId.String(), err) http.Error(resp, "ERROR: Unknown error occurred when negotiating MIME type.", http.StatusInternalServerError) return } @@ -388,7 +397,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { // `mime` URL query parameter. if params.Has("mime") { if parsedFmts, err = parseAccept(strings.Join(params["mime"], ",")); err != nil { - s.log.Err("server.Server.handleDefault: Failed to parse 'mime' URL parameter '%#v' for '%s': %v", params["mime"], remAddrPort, err) + s.log.Err("server.Server.handleDefault: Failed to parse 'mime' URL parameter '%#v' for '%s' (%s): %v", params["mime"], remAddrPort, reqId.String(), err) resp.Header()["Accept"] = okAcceptMime http.Error( resp, @@ -399,12 +408,12 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { } if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil { if errors.Is(err, ErrUnsupportedMIME) { - s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v'.", remAddrPort, params["mime"]) + s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' (%s) via '%#v'.", remAddrPort, reqId.String(), params["mime"]) resp.Header()["Accept"] = okAcceptMime http.Error(resp, "ERROR: No supported MIME type specified via URL parameter 'mime'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable) return } else { - s.log.Err("server.Server.handleDefault: Received unknown error choosing from 'mime' URL parameter for '%s': %v", remAddrPort, err) + s.log.Err("server.Server.handleDefault: Received unknown error choosing from 'mime' URL parameter for '%s' (%s): %v", remAddrPort, reqId.String(), err) http.Error(resp, "ERROR: Unknown error occurred when negotiating MIME type.", http.StatusInternalServerError) return } @@ -413,7 +422,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { // 'include' URL query parameter (only for text/html). if outerFmt == mediaHTML && params.Has("include") { if parsedFmts, err = parseAccept(strings.Join(params["include"], ",")); err != nil { - s.log.Err("server.Server.handleDefault: Failed to parse 'include' URL parameter '%#v' for '%s': %v", params["include"], remAddrPort, err) + s.log.Err("server.Server.handleDefault: Failed to parse 'include' URL parameter '%#v' for '%s' (%s): %v", params["include"], remAddrPort, reqId.String(), err) resp.Header()["Accept"] = okAcceptMime http.Error( resp, @@ -424,12 +433,12 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { } if includeFmt, err = decideParseAccept(parsedFmts, includeFmt); err != nil { if errors.Is(err, ErrUnsupportedMIME) { - s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v'.", remAddrPort, params["include"]) + s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' (%s) via '%#v'.", remAddrPort, reqId.String(), params["include"]) resp.Header()["Accept"] = okAcceptMime http.Error(resp, "ERROR: No supported MIME type specified via URL parameter 'include'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable) return } else { - s.log.Err("server.Server.handleDefault: Received unknown error choosing from 'include' URL parameter for '%s': %v", remAddrPort, err) + s.log.Err("server.Server.handleDefault: Received unknown error choosing from 'include' URL parameter for '%s' (%s): %v", remAddrPort, reqId.String(), err) http.Error(resp, "ERROR: Unknown error occurred when negotiating MIME type.", http.StatusInternalServerError) return } @@ -459,13 +468,13 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { case mediaYAML: renderer = s.renderYML default: - s.log.Err("server.Server.handleDefault: Unknown output format '%s'", outerFmt) + s.log.Err("server.Server.handleDefault: Unknown output format '%s' from '%s' (%s)", outerFmt, remAddrPort, reqId.String()) http.Error(resp, "ERROR: Unable to determine default renderer.", http.StatusInternalServerError) return } if err = renderer(page, resp); err != nil { - s.log.Err("server.Server.handleDefault: Failed to render request from '%s' as '%s': %v", remAddrPort, outerFmt, err) + s.log.Err("server.Server.handleDefault: Failed to render request from '%s' (%s) as '%s': %v", remAddrPort, reqId.String(), outerFmt, err) // The renderer handles the error-handling with the client. return } @@ -481,20 +490,23 @@ func (s *Server) handleAbout(resp http.ResponseWriter, req *http.Request) { Req: req, }, PageType: "about", + ReqUUID: uuid.New(), } - s.log.Debug("server.Server.handleAbout: Handling request:\n%s", spew.Sdump(req)) + s.log.Info("server.Server.handleAbout: '%s' (%s)", renderPage.ReqUUID.String(), req.RemoteAddr) + s.log.Debug("server.Server.handleAbout: Handling request for '%s':\n%s", renderPage.ReqUUID.String(), spew.Sdump(req)) resp.Header().Set("ClientInfo-Version", version.Ver.Short()) resp.Header().Set("Content-Type", "text/html; charset=utf-8") + resp.Header().Set(httpCIDHdr, renderPage.ReqUUID.String()) if err = tpl.ExecuteTemplate(resp, "about", renderPage); err != nil { - s.log.Err("server.Server.handleAbout: Failed to execute template for '%s': %v", req.RemoteAddr, err) + s.log.Err("server.Server.handleAbout: Failed to execute template for '%s': %v", renderPage.ReqUUID.String(), err) http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError) return } - s.log.Debug("server.Server.handleAbout: Handled request:\n%s", spew.Sdump(req)) + s.log.Debug("server.Server.handleAbout: Handled request for '%s'", renderPage.ReqUUID.String()) return } @@ -507,20 +519,23 @@ func (s *Server) handleUsage(resp http.ResponseWriter, req *http.Request) { Req: req, }, PageType: "usage", + ReqUUID: uuid.New(), } - s.log.Debug("server.Server.handleUsage: Handling request:\n%s", spew.Sdump(req)) + s.log.Info("server.Server.handleUsage: '%s' (%s)", renderPage.ReqUUID.String(), req.RemoteAddr) + s.log.Debug("server.Server.handleUsage: Handling request for '%s':\n%s", renderPage.ReqUUID.String(), spew.Sdump(req)) resp.Header().Set("ClientInfo-Version", version.Ver.Short()) resp.Header().Set("Content-Type", "text/html; charset=utf-8") + resp.Header().Set(httpCIDHdr, renderPage.ReqUUID.String()) if err = tpl.ExecuteTemplate(resp, "usage", renderPage); err != nil { - s.log.Err("server.Server.handleAbout: Failed to execute template for '%s': %v", req.RemoteAddr, err) + s.log.Err("server.Server.handleAbout: Failed to execute template for '%s' (%s): %v", renderPage.ReqUUID.String(), req.RemoteAddr, err) http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError) return } - s.log.Debug("server.Server.handleUsage: Handled request:\n%s", spew.Sdump(req)) + s.log.Debug("server.Server.handleUsage: Handled request for '%s'", renderPage.ReqUUID.String()) return } @@ -533,7 +548,7 @@ func (s *Server) renderJSON(page *Page, resp http.ResponseWriter) (err error) { if page.DoIndent { if b, err = json.MarshalIndent(page.Info, "", page.Indent); err != nil { - s.log.Err("server.Server.renderJSON: Failed to render to indented JSON: %v", err) + s.log.Err("server.Server.renderJSON: Failed to render to indented JSON for '%s': %v", page.ReqUUID.String(), err) http.Error(resp, "ERROR: Failed to render JSON", http.StatusInternalServerError) return } @@ -566,13 +581,13 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) { case mediaJSON, mediaXML: if page.DoIndent { if b, err = mediaIndent[*page.RawFmt](page.Info, "", page.Indent); err != nil { - s.log.Err("server.Server.renderHTML: Failed to render to indented include '%s': %v", *page.RawFmt, err) + s.log.Err("server.Server.renderHTML: Failed to render to indented include '%s' for '%s': %v", *page.RawFmt, page.ReqUUID.String(), err) http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError) return } } else { if b, err = mediaNoIndent[*page.RawFmt](page.Info); err != nil { - s.log.Err("server.Server.renderHTML: Failed to render to include '%s': %v", *page.RawFmt, err) + s.log.Err("server.Server.renderHTML: Failed to render to include '%s' for '%s': %v", *page.RawFmt, page.ReqUUID.String(), err) http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError) return } @@ -580,7 +595,9 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) { // Non-indentable case mediaYAML: if b, err = mediaNoIndent[*page.RawFmt](page.Info); err != nil { - s.log.Err("server.Server.renderHTML: Failed to render to '%s': %v", *page.RawFmt, err) + s.log.Err("server.Server.renderHTML: Failed to render to '%s' for '%s': %v", *page.RawFmt, page.ReqUUID.String(), err) + http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError) + return } } if b != nil { @@ -592,7 +609,7 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) { resp.Header().Set("Content-Type", "text/html; charset=utf-8") if err = tpl.ExecuteTemplate(resp, "index", page); err != nil { - s.log.Err("server.Server.renderHTML: Failed to render template: %v", err) + s.log.Err("server.Server.renderHTML: Failed to render template for '%s': %v", page.ReqUUID.String(), err) http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError) return } @@ -606,13 +623,13 @@ func (s *Server) renderXML(page *Page, resp http.ResponseWriter) (err error) { if page.DoIndent { if b, err = xml.MarshalIndent(page.Info, "", page.Indent); err != nil { - s.log.Err("server.Server.renderXML: Failed to render to indented XML: %v", err) + s.log.Err("server.Server.renderXML: Failed to render to indented XML for '%s': %v", page.ReqUUID.String(), err) http.Error(resp, "ERROR: Failed to render XML", http.StatusInternalServerError) return } } else { if b, err = xml.Marshal(page.Info); err != nil { - s.log.Err("server.Server.renderXML: Failed to render to XML: %v", err) + s.log.Err("server.Server.renderXML: Failed to render to XML for '%s': %v", page.ReqUUID.String(), err) http.Error(resp, "ERROR: Failed to render XML", http.StatusInternalServerError) return } @@ -621,7 +638,7 @@ func (s *Server) renderXML(page *Page, resp http.ResponseWriter) (err error) { resp.Header().Set("Content-Type", "application/xml; charset=utf-8") if _, err = resp.Write(b); err != nil { - s.log.Err("server.Server.renderXML: Failed to send XML: %v", err) + s.log.Err("server.Server.renderXML: Failed to send XML to '%s': %v", page.ReqUUID.String(), err) return } @@ -633,15 +650,15 @@ func (s *Server) renderYML(page *Page, resp http.ResponseWriter) (err error) { var b []byte if b, err = yaml.Marshal(page.Info); err != nil { - s.log.Err("server.Server.renderJSON: Failed to render to JSON: %v", err) - http.Error(resp, "ERROR: Failed to render JSON", http.StatusInternalServerError) + s.log.Err("server.Server.renderYML: Failed to render to YAML for '%s': %v", page.ReqUUID.String(), err) + http.Error(resp, "ERROR: Failed to render YAML", http.StatusInternalServerError) return } resp.Header().Set("Content-Type", "application/yaml") if _, err = resp.Write(b); err != nil { - s.log.Err("server.Server.renderJSON: Failed to send JSON: %v", err) + s.log.Err("server.Server.renderYML: Failed to send YAML to '%s': %v", page.ReqUUID.String(), err) return } diff --git a/server/funcs_tpl.go b/server/funcs_tpl.go index e4ce6d3..ae84193 100644 --- a/server/funcs_tpl.go +++ b/server/funcs_tpl.go @@ -2,9 +2,9 @@ package server import ( `fmt` + `html/template` `net/netip` `strings` - `html/template` ) func getIpver(a netip.Addr) (verStr string) { @@ -33,6 +33,6 @@ func getTitle(subPage string) (title string) { func safeUrl(urlStr string) (u template.URL) { u = template.URL(urlStr) - + return } diff --git a/server/tpl/meta.info.html.tpl b/server/tpl/meta.info.html.tpl index 60d1007..fa2265b 100644 --- a/server/tpl/meta.info.html.tpl +++ b/server/tpl/meta.info.html.tpl @@ -4,7 +4,7 @@ {{- $linkico := "🔗" }}

Client/Browser Information{{ $linkico }}

- Your IP{{ getIpver $page.Info.IP }} Address is {{ $page.Info.IP.String }}. + Your IP{{ getIpver $page.Info.IP }} Address is {{ $page.Info.IP.String }} (more info).
You are connecting with port {{ $page.Info.Port }} outbound. {{- if $page.HasAltURL }} @@ -22,9 +22,9 @@ const addr = dat.ip; const infoDiv = document.getElementById('alt_addr'); infoDiv.innerHTML = ` - You also have IP{{ $page.AltVer }} address ${addr}. + You also have IP{{ $page.AltVer }} address ${addr} (more info).
- Try loading {{ $page.AltURL }} for more information.`; + Try loading {{ $page.AltURL }} to check your secondary address.`; } } catch (error) { console.info('Did not fetch alternate address: ', error); diff --git a/server/tpl/meta.top.html.tpl b/server/tpl/meta.top.html.tpl index 1648f79..04eadbb 100644 --- a/server/tpl/meta.top.html.tpl +++ b/server/tpl/meta.top.html.tpl @@ -4,6 +4,7 @@ {{- $linkico := "🔗" -}} + {{ getTitle $page.PageType }} diff --git a/server/types.go b/server/types.go index 06793e6..fbcc319 100644 --- a/server/types.go +++ b/server/types.go @@ -8,6 +8,7 @@ import ( "net/url" "os" + "github.com/google/uuid" "github.com/mileusna/useragent" "r00t2.io/clientinfo/args" "r00t2.io/goutils/logging" @@ -87,7 +88,9 @@ type Page struct { Indent string // DoIndent indicates if indenting was enabled. DoIndent bool - srv *Server + // ReqUUID is a unique request ID assigned. + ReqUUID uuid.UUID + srv *Server } type Server struct {