Initial public release
This commit is contained in:
brent saner 2024-12-12 03:47:30 -05:00
parent db081e2699
commit 3967e5fdb7
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
4 changed files with 108 additions and 293 deletions

View File

@ -1,12 +1,10 @@
package server package server


import ( import (
`crypto/tls`
`encoding/json` `encoding/json`
`encoding/xml` `encoding/xml`
"errors" "errors"
"fmt" "fmt"
`mime/multipart`
"net" "net"
"net/http" "net/http"
`net/http/fcgi` `net/http/fcgi`
@ -267,246 +265,6 @@ func (s *Server) explicit404(resp http.ResponseWriter, req *http.Request) {


func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {


var vals url.Values
var uaVals []string
var doInclude bool
var doIndent bool
var err error
var ok bool
var b []byte
var remAddrPort string
var okMedia []string
var nAP netip.AddrPort
var parsedFmts []*parsedMIME
var renderPage *Page = new(Page)
var format string = mediaJSON
var indent string = " "
var client *R00tInfo = new(R00tInfo)

renderPage.RawIndent = " "
renderPage.PageType = "index"

s.log.Debug("server.Server.handleDefault: Handling request:\n%s", spew.Sdump(req))

/*
if req.URL != nil &&
req.URL.Path != "" &&
req.URL.Path != "/" &&
req.URL.Path != "/index" &&
req.URL.Path != "/index.html" {
resp.WriteHeader(http.StatusNotFound)
}
*/

client.Req = req
remAddrPort = req.RemoteAddr
if s.isHttp && req.Header.Get(httpRealHdr) != "" {
remAddrPort = req.Header.Get(httpRealHdr)
req.Header.Del(httpRealHdr)
}
if remAddrPort != "" {
if nAP, err = netip.ParseAddrPort(remAddrPort); err != nil {
s.log.Err("server.Server.handleDefault: Failed to parse remote address '%s': %v", req.RemoteAddr, err)
// Don't return an error in case we're doing weird things like direct socket clients.
err = nil
/*
http.Error(resp, "ERROR: Failed to parse client address", http.StatusInternalServerError)
return
*/
}
client.IP = net.ParseIP(nAP.Addr().String())
client.Port = nAP.Port()
}
client.Headers = XmlHeaders(req.Header)

uaVals = req.Header.Values("User-Agent")
if uaVals != nil && len(uaVals) > 0 {
client.Client = make([]*R00tClient, len(uaVals))
for idx, ua := range uaVals {
if client.Client[idx], err = NewClient(ua); err != nil {
s.log.Err("server.Server.handleDefault: Failed to create client for '%s': %v", ua, err)
http.Error(resp, fmt.Sprintf("ERROR: Failed to parse 'User-Agent' '%s'", ua), http.StatusInternalServerError)
return
}
}
}
if client.Client != nil && len(client.Client) > 0 {
// Check the passed UAs for a browser. We then change the "default" format if so.
for _, ua := range client.Client {
if ua.IsMobile || ua.IsDesktop {
format = mediaHTML
break
}
}
}
renderPage.Info = client

vals = req.URL.Query()

// Determine the format/MIME type of the response.
if vals.Has("mime") {
format = req.URL.Query().Get("mime")
} else {
if parsedFmts, err = parseAccept(strings.Join(req.Header.Values("Accept"), ",")); err != nil {
s.log.Err("server.Server.handleDefault: Failed to parse Accept header: %v", err)
http.Error(
resp,
"ERROR: Invalid 'Accept' header value; see RFC 9110 § 12.5.1 and https://www.iana.org/assignments/media-types/media-types.xhtml",
http.StatusBadRequest,
)
return
}
if format, err = decideParseAccept(parsedFmts, mediaJSON); err != nil {
if errors.Is(err, ErrUnsupportedMIME) {
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s'.", req.RemoteAddr)
for mt := range mediaNoIndent {
okMedia = append(okMedia, mt)
}
req.Header.Set("Accept", strings.Join(okMedia, ", "))
http.Error(resp, "ERROR: No supported MIME type specified; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
return
} else {
s.log.Err("server.Server.handleDefault: Received unknown error choosing an Accept header for '%s': %v", req.RemoteAddr, err)
http.Error(resp, "ERROR: Unknown error occurred when negotiationg MIME type.", http.StatusInternalServerError)
return
}
}
}
s.log.Debug("server.Server.handleDefault: Using format '%s' for '%s'", format, req.RemoteAddr)
// If it's HTML and they want an include, that needs to be validated too.
if format == mediaHTML && vals.Has("include") {
doInclude = true
if parsedFmts, err = parseAccept(strings.Join(vals["include"], ", ")); err != nil {
s.log.Err("server.Server.handleDefault: Failed to parse include parameter: %v", err)
http.Error(
resp,
"ERROR: Invalid 'include' parameter value; see RFC 9110 § 12.5.1 and https://www.iana.org/assignments/media-types/media-types.xhtml",
http.StatusBadRequest,
)
return
}
if renderPage.RawFmt, err = decideParseAccept(parsedFmts, format); err != nil {
if errors.Is(err, ErrUnsupportedMIME) {
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%#v' 'include'.", vals["include"], req.RemoteAddr)
for mt := range mediaNoIndent {
okMedia = append(okMedia, mt)
}
req.Header.Set("Accept", strings.Join(okMedia, ", "))
http.Error(resp, "ERROR: No supported MIME type specified for 'include'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
return
} else {
s.log.Err("server.Server.handleDefault: Received unknown error choosing an include format for '%s': %v", req.RemoteAddr, err)
http.Error(resp, "ERROR: Unknown error occurred when negotiationg MIME type.", http.StatusInternalServerError)
return
}
}
// The indentation is set below.
}

// Determine indentation (if the format even supports it).
if format == mediaHTML {
if doInclude {
if _, ok = mediaIndent[renderPage.RawFmt]; ok {
doIndent = vals.Has("indent")
if doIndent {
if req.URL.Query().Get("indent") != "" {
renderPage.RawIndent = req.URL.Query().Get("indent")
renderPage.DoRawIndent = true
}
}
} else if _, ok = mediaNoIndent[renderPage.RawFmt]; !ok {
// It's not a supported MIME.
s.log.Err("server.Server.handleDefault: Requested MIME type '%s' for '%s' unsupported.", renderPage.RawFmt, req.RemoteAddr)
for mt := range mediaNoIndent {
okMedia = append(okMedia, mt)
}
req.Header.Set("Accept", strings.Join(okMedia, ", "))
http.Error(
resp,
fmt.Sprintf("ERROR: MIME type '%s' unsupported for 'include'; see Accept header in response for valid types.", renderPage.RawFmt),
http.StatusNotAcceptable,
)
return
} else {
// This seems backwards, but "non-indented" formats actually need indenting enabled so their whitespace renders properly.
renderPage.DoRawIndent = true
}
}
} else {
if _, ok = mediaIndent[format]; ok {
doIndent = vals.Has("indent")
if doIndent {
if req.URL.Query().Get("indent") != "" {
indent = req.URL.Query().Get("indent")
}
}
} else if _, ok = mediaNoIndent[format]; !ok {
// It's not a supported MIME.
s.log.Err("server.Server.handleDefault: Requested MIME type '%s' for '%s' unsupported.", format, req.RemoteAddr)
for mt := range mediaNoIndent {
okMedia = append(okMedia, mt)
}
req.Header.Set("Accept", strings.Join(okMedia, ", "))
http.Error(resp, fmt.Sprintf("ERROR: MIME type '%s' unsupported; see Accept header in response for valid types.", format), http.StatusNotAcceptable)
return
}
}

// Now render the response.
if format == mediaHTML {
// This gets special treatment since it's templated.
resp.Header().Set("Content-Type", "text/html; charset=utf-8")
if doInclude {
renderPage.Raw = new(string)
if doIndent {
if b, err = mediaIndent[renderPage.RawFmt](client, "", renderPage.RawIndent); err != nil {
s.log.Err("server.Server.handleDefault: Failed to render indented raw '%s' for '%s': %v", renderPage.RawFmt, req.RemoteAddr, err)
http.Error(resp, fmt.Sprintf("ERROR: Failed to render 'include' '%s'", renderPage.RawFmt), http.StatusInternalServerError)
return
}
} else {
if b, err = mediaNoIndent[renderPage.RawFmt](client); err != nil {
s.log.Err("server.Server.handleDefault: Failed to render raw '%s' for '%s': %v", renderPage.RawFmt, req.RemoteAddr, err)
http.Error(resp, fmt.Sprintf("ERROR: Failed to render '%s'", renderPage.RawFmt), http.StatusInternalServerError)
return
}
}
*renderPage.Raw = string(b)
}
if err = tpl.ExecuteTemplate(resp, "index", renderPage); err != nil {
s.log.Err("server.Server.handleDefault: Failed to execute template for '%s': %v", req.RemoteAddr, err)
http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
return
}
} else {
resp.Header().Set("Content-Type", format)
if doIndent {
// This was already filtered to valid specified MIME above.
if b, err = mediaIndent[format](client, "", indent); err != nil {
s.log.Err("server.Server.handleDefault: Failed to render indented '%s' for '%s': %v", format, req.RemoteAddr, err)
http.Error(resp, fmt.Sprintf("ERROR: Failed to render '%s'", format), http.StatusInternalServerError)
return
}
} else {
if b, err = mediaNoIndent[format](client); err != nil {
s.log.Err("server.Server.handleDefault: Failed to render '%s' for '%s': %v", format, req.RemoteAddr, err)
http.Error(resp, fmt.Sprintf("ERROR: Failed to render '%s'", format), http.StatusInternalServerError)
return
}
}
if _, err = resp.Write(b); err != nil {
s.log.Err("server.Server.handleDefault: Failed to serve indented '%s' to '%s': %v", format, req.RemoteAddr, err)
return
}
}

s.log.Debug("server.Server.handleDefault: Handled request:\n%s", spew.Sdump(req))

return
}

func (s *Server) handleDefaultNew(resp http.ResponseWriter, req *http.Request) {

var err error var err error
var page *Page var page *Page
var uas []string var uas []string
@ -602,7 +360,7 @@ func (s *Server) handleDefaultNew(resp http.ResponseWriter, req *http.Request) {
if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil { if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil {
if errors.Is(err, ErrUnsupportedMIME) { 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'.", remAddrPort, reqdMimes)
req.Header["Accept"] = okAcceptMime 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) http.Error(resp, "ERROR: No supported MIME type specified via request 'Accept'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
return return
} else { } else {
@ -627,7 +385,7 @@ func (s *Server) handleDefaultNew(resp http.ResponseWriter, req *http.Request) {
if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil { if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil {
if errors.Is(err, ErrUnsupportedMIME) { 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' via '%#v'.", remAddrPort, params["mime"])
req.Header["Accept"] = okAcceptMime 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) http.Error(resp, "ERROR: No supported MIME type specified via URL parameter 'mime'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
return return
} else { } else {
@ -652,7 +410,7 @@ func (s *Server) handleDefaultNew(resp http.ResponseWriter, req *http.Request) {
if includeFmt, err = decideParseAccept(parsedFmts, includeFmt); err != nil { if includeFmt, err = decideParseAccept(parsedFmts, includeFmt); err != nil {
if errors.Is(err, ErrUnsupportedMIME) { 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' via '%#v'.", remAddrPort, params["include"])
req.Header["Accept"] = okAcceptMime 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) http.Error(resp, "ERROR: No supported MIME type specified via URL parameter 'include'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
return return
} else { } else {
@ -791,7 +549,7 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) {
return return
} }
} else { } else {
if b, err = mediaNoIndent[*page.RawFmt](page.Indent); err != nil { 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': %v", *page.RawFmt, err)
http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError) http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError)
return return

View File

@ -11,7 +11,7 @@
{{- if $page.Raw }} {{- if $page.Raw }}
<h3 id="client_raw">Raw Block ({{ $page.RawFmt }})<a href="#client_raw">{{ $linkico }}</a></h3> <h3 id="client_raw">Raw Block ({{ $page.RawFmt }})<a href="#client_raw">{{ $linkico }}</a></h3>
<p> <p>
{{- if $page.DoRawIndent }} {{- if $page.DoIndent }}
<pre>{{ $page.Raw }}</pre> <pre>{{ $page.Raw }}</pre>
{{- else }} {{- else }}
<code>{{ $page.Raw }}</code> <code>{{ $page.Raw }}</code>
@ -20,36 +20,64 @@
{{- end }} {{- end }}
<h3 id="client_ua">User Agent Information<a href="#client_ua">{{ $linkico }}</a></h3> <h3 id="client_ua">User Agent Information<a href="#client_ua">{{ $linkico }}</a></h3>
<p>This is information that your browser sends to identify itself.</p> <p>This is information that your browser sends to identify itself.</p>
{{- range $idx, $ua := $page.Info.Client }}
<p> <p>
{{- range $idx, $ua := $page.Info.Client }}
User Agent ({{ $idx }}): User Agent ({{ $idx }}):
<div class="tg-wrap">
<table>
<thead>
<tr>
<th>Identifier</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{{- $flds := $ua.ToMap }}
{{- range $fld, $str := $flds }}
<tr>
<td>{{ $fld }}</td>
<td>{{ $str }}</td>
</tr>
{{- end }}
</tbody>
</table>
</div>
{{- /*
<ul> <ul>

{{- $flds := $ua.ToMap }} {{- $flds := $ua.ToMap }}
{{- range $fld, $str := $flds }} {{- range $fld, $str := $flds }}
<li><b>{{ $fld }}:</b> {{ $str }}</li> <li><b>{{ $fld }}:</b> {{ $str }}</li>
{{- end }} {{- end }}
</ul> </ul>
{{- end }} */}}
</p> </p>
{{- end }}
<h3 id="client_hdrs">Request Headers<a href="#client_hdrs">{{ $linkico }}</a></h3> <h3 id="client_hdrs">Request Headers<a href="#client_hdrs">{{ $linkico }}</a></h3>
<p> <p>
These are headers sent along with the request your browser sends for the page's content. These are headers sent along with the request your browser sends for the page's content.
Note that some headers may have multiple values. Note that some headers may have multiple values.
</p> </p>
<p> <p>
<table> <div class="tg-wrap">
<tr> <table>
<th>Header</th> <thead>
<th>Value</th> <tr>
</tr> <th>Header</th>
{{- range $hdrNm, $hdrVals := $page.Info.Req.Header }} <th>Value</th>
<tr> </tr>
{{- range $val := $hdrVals }} </thead>
<td>{{ $hdrNm }}</td> <tbody>
<td>{{ $val }}</td> {{- range $hdrNm, $hdrVals := $page.Info.Req.Header }}
{{- end }} <tr>
</tr> {{- range $val := $hdrVals }}
{{- end }} <td>{{ $hdrNm }}</td>
</table> <td>{{ $val }}</td>
{{- end }}
</tr>
{{- end }}
</tbody>
</table>
</div>
</p> </p>
{{- end }} {{- end }}

View File

@ -5,8 +5,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ getTitle $page.PageType }}</title> <title>{{ getTitle $page.PageType }}</title>
{{- /*
<!-- Bootstrap core CSS --> <!-- Bootstrap core CSS -->
<!-- <!--
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
@ -24,30 +25,51 @@
<!-- <!--
<link href="https://getbootstrap.com/docs/4.0/examples/offcanvas/offcanvas.css" rel="stylesheet"> <link href="https://getbootstrap.com/docs/4.0/examples/offcanvas/offcanvas.css" rel="stylesheet">
--> -->
*/}}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/bootswatch@5.3/dist/lux/bootstrap.min.css" rel="stylesheet">
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<div class="header clearfix"> <div class="header clearfix">
<nav> <nav class="navbar navbar-expand-lg bg-body-tertiary">
<ul class="nav nav-pills pull-right"> <div class="container-fluid">
<li role="presentation"><a href="/">Home</a></li> <a class="navbar-brand" href="/">Home</a>
<li role="presentation"><a href="/about">About</a></li> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<li role="presentation"><a href="/usage">Usage</a></li> <span class="navbar-toggler-icon"></span>
<ul role="presentation"> </button>
<li><a href="/?mime=application/json&indent">JSON</a></li> <div class="collapse navbar-collapse" id="navbarNav">
<li><a href="/?mime=application/xml&indent">XML</a></li> <ul class="navbar-nav ms-auto"> <!-- ms-auto replaces pull-right for alignment -->
<li><a href="/?mime=application/yaml">YAML</a></li> <li class="nav-item">
<li><a href="/?mime=text/html">HTML (This Page)</a></li> <a class="nav-link" href="/">Home</a>
</ul> </li>
<!-- <li class="nav-item">
the following opens in a new tab/window/whatever. <a class="nav-link" href="/about">About</a>
the line after opens in the same tab/window/etc. </li>
--> <li class="nav-item">
<!-- <a class="nav-link" href="/usage">Usage</a>
<li role="presentation"><a href="https://r00t2.io/" target="_blank">r00t^2</a></li> </li>
--> <!-- Dropdown Menu -->
<li role="presentation"><a href="https://r00t2.io/">r00t^2</a></li> <li class="nav-item dropdown">
</ul> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">Formats/MIME types</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="/?mime=application/json&indent">JSON (<code>application/json</code>)</a></li>
<li><a class="dropdown-item" href="/?mime=application/xml&indent">XML (<code>application/xml</code>)</a></li>
<li><a class="dropdown-item" href="/?mime=application/yaml">YAML (<code>application/yaml</code>)</a></li>
{{- if eq $page.PageType "index" }}
<li><a class="dropdown-item" href="/?mime=text/html">HTML (this page) (<code>text/html</code>)</a></li>
{{- else }}
<li><a class="dropdown-item" href="/?mime=text/html">HTML (<code>text/html</code>)</a></li>
{{- end }}
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="https://r00t2.io/" target="_blank">r00t^2</a>
</li>
</ul>
</div>
</div>
</nav> </nav>
</div> </div>
{{- end }} {{- end }}

View File

@ -17,10 +17,10 @@
You can force a specific raw output by specifying the <a href="https://www.iana.org/assignments/media-types/media-types.xhtml">MIME type</a> via You can force a specific raw output by specifying the <a href="https://www.iana.org/assignments/media-types/media-types.xhtml">MIME type</a> via
<a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.1">the <code>Accept</code> header (RFC 9110 &sect; 12.5.1)</a>, which may be one of: <a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.1">the <code>Accept</code> header (RFC 9110 &sect; 12.5.1)</a>, which may be one of:
<ul> <ul>
<li><code>application/json</code> for <a href="https://www.rfc-editor.org/rfc/rfc8259.html">JSON</a></li> <li><code>application/json</code> for JSON (<a href="https://www.rfc-editor.org/rfc/rfc8259.html">RFC 8259</a>, <a href="https://www.iana.org/assignments/media-types/application/json">IANA registration</a>)</li>
<li><code>application/xml</code> for <a href="https://www.rfc-editor.org/rfc/rfc7303.html">XML</a></li> <li><code>application/xml</code> for XML (<a href="https://www.rfc-editor.org/rfc/rfc7303.html">RFC 7303</a>, <a href="https://www.iana.org/assignments/media-types/application/xml">IANA registration</a>)</li>
<li><code>application/yaml</code> for <a href="https://www.rfc-editor.org/rfc/rfc9512.html">YAML</a></li> <li><code>application/yaml</code> for YAML (<a href="https://www.rfc-editor.org/rfc/rfc9512.html">RFC 9512</a>, <a href="https://www.iana.org/assignments/media-types/application/yaml">IANA registration</a>)</li>
<li><code>text/html</code> for <a href="https://www.rfc-editor.org/rfc/rfc2854.html">HTML</a></li> <li><code>text/html</code> for HTML (<a href="https://www.rfc-editor.org/rfc/rfc2854.html">RFC 2854</a>, <a href="https://www.iana.org/assignments/media-types/text/html">IANA registration</a>)</li>
</ul> </ul>
For example: <code>Accept: application/json</code> will return JSON. For example: <code>Accept: application/json</code> will return JSON.
<br/> <br/>
@ -36,8 +36,15 @@


If no selectable MIME type is provided but an <code>Accept</code> was given, an error will be returned; specifically, a If no selectable MIME type is provided but an <code>Accept</code> was given, an error will be returned; specifically, a
<a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.7"><code>406</code> status code (RFC 9110 &sect; 15.5.7)</a>. <a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.7"><code>406</code> status code (RFC 9110 &sect; 15.5.7)</a>.
In this case, supported MIME types will be returned in the response's <code>Accept</code> header. In this case, supported MIME types will be returned in the response's <code>Accept</code> header values, e.g.:
<br/> <p>
<pre>
Accept: application/json
Accept: application/xml
Accept: application/yaml
Accept: text/html
</pre>
</p>


Note that <a href="https://lynx.invisible-island.net/">Lynx</a> and <a href="http://elinks.or.cz/">Elinks</a> are considered "graphical" Note that <a href="https://lynx.invisible-island.net/">Lynx</a> and <a href="http://elinks.or.cz/">Elinks</a> are considered "graphical"
browsers by this program as they are HTML-centric. browsers by this program as they are HTML-centric.
@ -46,7 +53,7 @@
The following parameters control/modify behavior.<a href="#usage_params_mod">{{ $linkico }}</a> The following parameters control/modify behavior.<a href="#usage_params_mod">{{ $linkico }}</a>
<ul> <ul>
<li> <li>
<b>mime:</b> Specify an explicit MIME type via URL instead of the <code>Accept</code> header as specified above. <b><code>mime</code>:</b> Specify an explicit MIME type via URL instead of the <code>Accept</code> header as specified above.
<ul> <ul>
<li>This should only be used by clients in which it is impossible or particularly cumbersome to modify/specify headers. <li>This should only be used by clients in which it is impossible or particularly cumbersome to modify/specify headers.
<code>Accept</code> is more performant.</li> <code>Accept</code> is more performant.</li>
@ -56,7 +63,7 @@
</ul> </ul>
</li> </li>
<li> <li>
<b>include:</b> Include a <code>&lt;code&gt;</code> (or <code>&lt;pre&gt;</code>, depending on if indentation is needed/requested) block in the HTML for the specified MIME type as well.</li> <b><code>include</code>:</b> Include a <code>&lt;code&gt;</code> (or <code>&lt;pre&gt;</code>, depending on if indentation is needed/requested) block in the HTML for the specified MIME type as well.</li>
<ul> <ul>
<li>Only the first supported instance of this parameter will be used.</li> <li>Only the first supported instance of this parameter will be used.</li>
<li> <li>
@ -70,7 +77,7 @@
</ul> </ul>
</li> </li>
<li> <li>
<b>indent:</b> Enable/specify indentation for JSON and XML output; ignored for others. <b><code>indent</code>:</b> Enable/specify indentation for JSON and XML output; ignored for others.
<ul> <ul>
<li>The default is to not indent. (Commonly referred to as "condensed" or "compressed" JSON/XML.)</li> <li>The default is to not indent. (Commonly referred to as "condensed" or "compressed" JSON/XML.)</li>
<li>Only the first specified instance of this parameter will be used.</li> <li>Only the first specified instance of this parameter will be used.</li>