mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
rtl8720dn: add examples/rtl8720dn/webserver
This commit is contained in:
@@ -199,6 +199,8 @@ endif
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=wioterminal ./examples/rtl8720dn/webclient/
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=wioterminal ./examples/rtl8720dn/webserver/
|
||||
@md5sum ./build/test.hex
|
||||
|
||||
DRIVERS = $(wildcard */)
|
||||
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net/http"
|
||||
)
|
||||
|
||||
// You can override the setting with the init() in another source code.
|
||||
// func init() {
|
||||
// ssid = "your-ssid"
|
||||
// password = "your-password"
|
||||
// debug = true
|
||||
// }
|
||||
|
||||
var (
|
||||
ssid string
|
||||
password string
|
||||
debug = false
|
||||
)
|
||||
|
||||
var led = machine.LED
|
||||
var backlight = machine.LCD_BACKLIGHT
|
||||
|
||||
func main() {
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
backlight.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
err := run()
|
||||
for err != nil {
|
||||
fmt.Printf("error: %s\r\n", err.Error())
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
rtl, err := setupRTL8720DN()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http.UseDriver(rtl)
|
||||
|
||||
err = rtl.ConnectToAP(ssid, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ip, subnet, gateway, err := rtl.GetIP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("IP Address : %s\r\n", ip)
|
||||
fmt.Printf("Mask : %s\r\n", subnet)
|
||||
fmt.Printf("Gateway : %s\r\n", gateway)
|
||||
|
||||
http.HandleFunc("/", root)
|
||||
http.HandleFunc("/hello", hello)
|
||||
http.HandleFunc("/cnt", cnt)
|
||||
http.HandleFunc("/6", sixlines)
|
||||
http.HandleFunc("/off", LED_OFF)
|
||||
http.HandleFunc("/on", LED_ON)
|
||||
if err := http.ListenAndServe(":80", nil); err != nil {
|
||||
message(err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func root(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, `
|
||||
<html>
|
||||
<head>
|
||||
<title>TinyGo HTTP Server</title>
|
||||
<script language="javascript" type="text/javascript">
|
||||
var counter = 0
|
||||
function ledOn() { fetch("/on").then(response => response.text()).then(text => { led.innerHTML = "<p>on</p>"; }); }
|
||||
function ledOff() { fetch("/off").then(response => response.text()).then(text => { led.innerHTML = "<p>off</p>"; }); }
|
||||
function fetchCnt() { fetch("/cnt").then(response => response.json()).then(json => { counter = json.cnt; cnt.innerHTML = counter; }); }
|
||||
function incrCnt() { counter = counter + 1; fetch("/cnt?cnt=" + counter, { method: 'POST' }).then(response => response.json()).then(json => { counter = json.cnt; cnt.innerHTML = counter; }); }
|
||||
function setCnt() { fetch("/cnt", {
|
||||
method: "POST",
|
||||
body: "cnt=" + document.getElementsByName("cnt")[0].value,
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
}).then(response => response.json()).then(json => { counter = json.cnt; cnt.innerHTML = counter; }); return false; }
|
||||
function onLoad() { fetchCnt(); }
|
||||
</script>
|
||||
</head>
|
||||
<body onLoad="onLoad()">
|
||||
<h5>TinyGo HTTP Server</h5>
|
||||
<a href="/hello">/hello</a><br>
|
||||
<a href="/6">/6</a><br>
|
||||
|
||||
<p>
|
||||
LED<br>
|
||||
<a href="javascript:ledOn();">/on</a><br>
|
||||
<a href="javascript:ledOff();">/off</a><br>
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
<a href="/cnt">/cnt</a><br>
|
||||
cnt: <span id="cnt"></span><br>
|
||||
<a href="javascript:incrCnt()">incrCnt()</a><br>
|
||||
<form id="form1" style="display: inline" onSubmit="return setCnt()">
|
||||
<input type="text" name="cnt">
|
||||
<input type="button" value="set cnt", onClick="setCnt()">
|
||||
</form>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
}
|
||||
|
||||
func sixlines(w http.ResponseWriter, r *http.Request) {
|
||||
// https://fukuno.jig.jp/3267
|
||||
fmt.Fprint(w, `<body onload='onkeydown=e=>K=parseInt(e.key[5]||6,28)/3-8;Z=X=[B=A=12];Y=_=>`+
|
||||
`{for(C=[q=c=i=4];f=i--*K;c-=!Z[h+(K+6?p+K:C[i]=p*A-(p/9|0)*145)])p=B[i];for(c?0:K+6?h+=K:B=C;`+
|
||||
`i=K=q--;f+=Z[A+p])X[p=h+B[q]]=1;h+=A;if(f|B)for(Z=X,X=[l=228],B=[[-7,-20,6,h=17,-9,3,3][t=++t%7]-4,0,1,t-6?-A:2];l--;)`+
|
||||
`for(l%A?l-=l%A*!Z[l]:(P++,c=l+=A);--c>A;)Z[c]=Z[c-A];for(S="";i<240;S+=X[i]|(X[i]=Z[i]|=++i%A<2|i>228)?i%A?"■":"■<br>":" ");`+
|
||||
`D.innerHTML=S+P;setTimeout(Y,i-P)};Y(h=K=t=P=0)'id=D>`)
|
||||
}
|
||||
|
||||
func LED_ON(w http.ResponseWriter, r *http.Request) {
|
||||
led.High()
|
||||
backlight.High()
|
||||
w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
|
||||
fmt.Fprintf(w, "led.High()")
|
||||
}
|
||||
|
||||
func LED_OFF(w http.ResponseWriter, r *http.Request) {
|
||||
led.Low()
|
||||
backlight.Low()
|
||||
w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
|
||||
fmt.Fprintf(w, "led.Low()")
|
||||
}
|
||||
|
||||
func hello(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
|
||||
fmt.Fprintf(w, "hello")
|
||||
}
|
||||
|
||||
var counter int
|
||||
|
||||
func cnt(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
if r.Method == "POST" {
|
||||
c := r.Form.Get("cnt")
|
||||
if c != "" {
|
||||
i64, _ := strconv.ParseInt(c, 0, 0)
|
||||
counter = int(i64)
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set(`Content-Type`, `application/json`)
|
||||
fmt.Fprintf(w, `{"cnt": %d}`, counter)
|
||||
}
|
||||
|
||||
func message(msg string) {
|
||||
println(msg, "\r")
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// +build wioterminal
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"device/sam"
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/rtl8720dn"
|
||||
)
|
||||
|
||||
var (
|
||||
uart UARTx
|
||||
)
|
||||
|
||||
func handleInterrupt(interrupt.Interrupt) {
|
||||
// should reset IRQ
|
||||
uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
|
||||
uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
|
||||
}
|
||||
|
||||
func setupRTL8720DN() (*rtl8720dn.RTL8720DN, error) {
|
||||
machine.RTL8720D_CHIP_PU.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
machine.RTL8720D_CHIP_PU.Low()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
machine.RTL8720D_CHIP_PU.High()
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
if debug {
|
||||
waitSerial()
|
||||
}
|
||||
|
||||
uart = UARTx{
|
||||
UART: &machine.UART{
|
||||
Buffer: machine.NewRingBuffer(),
|
||||
Bus: sam.SERCOM0_USART_INT,
|
||||
SERCOM: 0,
|
||||
},
|
||||
}
|
||||
|
||||
uart.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, handleInterrupt)
|
||||
uart.Configure(machine.UARTConfig{TX: machine.PB24, RX: machine.PC24, BaudRate: 614400})
|
||||
|
||||
rtl := rtl8720dn.New(uart)
|
||||
rtl.Debug(debug)
|
||||
|
||||
_, err := rtl.Rpc_tcpip_adapter_init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rtl, nil
|
||||
}
|
||||
|
||||
// Wait for user to open serial console
|
||||
func waitSerial() {
|
||||
for !machine.Serial.DTR() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
type UARTx struct {
|
||||
*machine.UART
|
||||
}
|
||||
|
||||
func (u UARTx) Read(p []byte) (n int, err error) {
|
||||
if u.Buffered() == 0 {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
return 0, nil
|
||||
}
|
||||
return u.UART.Read(p)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ go 1.15
|
||||
require (
|
||||
github.com/eclipse/paho.mqtt.golang v1.2.0
|
||||
github.com/frankban/quicktest v1.10.2
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
|
||||
tinygo.org/x/tinyfont v0.2.1
|
||||
tinygo.org/x/tinyfs v0.1.0
|
||||
tinygo.org/x/tinyterm v0.1.0
|
||||
|
||||
@@ -11,6 +11,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package http
|
||||
|
||||
type DeviceDriver interface {
|
||||
ListenAndServe(addr string, handler Handler) error
|
||||
}
|
||||
|
||||
var ActiveDevice DeviceDriver
|
||||
|
||||
func UseDriver(driver DeviceDriver) {
|
||||
// TODO: rethink and refactor this
|
||||
if ActiveDevice != nil {
|
||||
panic("net.ActiveDevice is already set")
|
||||
}
|
||||
ActiveDevice = driver
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http/httptrace"
|
||||
"net/textproto"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Header represents the key-value pairs in an HTTP header.
|
||||
//
|
||||
// The keys should be in canonical form, as returned by
|
||||
// CanonicalHeaderKey.
|
||||
type Header map[string][]string
|
||||
|
||||
// Add adds the key, value pair to the header.
|
||||
// It appends to any existing values associated with key.
|
||||
// The key is case insensitive; it is canonicalized by
|
||||
// CanonicalHeaderKey.
|
||||
func (h Header) Add(key, value string) {
|
||||
textproto.MIMEHeader(h).Add(key, value)
|
||||
}
|
||||
|
||||
// Set sets the header entries associated with key to the
|
||||
// single element value. It replaces any existing values
|
||||
// associated with key. The key is case insensitive; it is
|
||||
// canonicalized by textproto.CanonicalMIMEHeaderKey.
|
||||
// To use non-canonical keys, assign to the map directly.
|
||||
func (h Header) Set(key, value string) {
|
||||
textproto.MIMEHeader(h).Set(key, value)
|
||||
}
|
||||
|
||||
// Get gets the first value associated with the given key. If
|
||||
// there are no values associated with the key, Get returns "".
|
||||
// It is case insensitive; textproto.CanonicalMIMEHeaderKey is
|
||||
// used to canonicalize the provided key. To use non-canonical keys,
|
||||
// access the map directly.
|
||||
func (h Header) Get(key string) string {
|
||||
return textproto.MIMEHeader(h).Get(key)
|
||||
}
|
||||
|
||||
// Values returns all values associated with the given key.
|
||||
// It is case insensitive; textproto.CanonicalMIMEHeaderKey is
|
||||
// used to canonicalize the provided key. To use non-canonical
|
||||
// keys, access the map directly.
|
||||
// The returned slice is not a copy.
|
||||
func (h Header) Values(key string) []string {
|
||||
return textproto.MIMEHeader(h).Values(key)
|
||||
}
|
||||
|
||||
// get is like Get, but key must already be in CanonicalHeaderKey form.
|
||||
func (h Header) get(key string) string {
|
||||
if v := h[key]; len(v) > 0 {
|
||||
return v[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// has reports whether h has the provided key defined, even if it's
|
||||
// set to 0-length slice.
|
||||
func (h Header) has(key string) bool {
|
||||
_, ok := h[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Del deletes the values associated with key.
|
||||
// The key is case insensitive; it is canonicalized by
|
||||
// CanonicalHeaderKey.
|
||||
func (h Header) Del(key string) {
|
||||
textproto.MIMEHeader(h).Del(key)
|
||||
}
|
||||
|
||||
// Write writes a header in wire format.
|
||||
func (h Header) Write(w io.Writer) error {
|
||||
return h.write(w, nil)
|
||||
}
|
||||
|
||||
func (h Header) write(w io.Writer, trace *httptrace.ClientTrace) error {
|
||||
return h.writeSubset(w, nil, trace)
|
||||
}
|
||||
|
||||
// Clone returns a copy of h or nil if h is nil.
|
||||
func (h Header) Clone() Header {
|
||||
if h == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find total number of values.
|
||||
nv := 0
|
||||
for _, vv := range h {
|
||||
nv += len(vv)
|
||||
}
|
||||
sv := make([]string, nv) // shared backing array for headers' values
|
||||
h2 := make(Header, len(h))
|
||||
for k, vv := range h {
|
||||
n := copy(sv, vv)
|
||||
h2[k] = sv[:n:n]
|
||||
sv = sv[n:]
|
||||
}
|
||||
return h2
|
||||
}
|
||||
|
||||
var timeFormats = []string{
|
||||
TimeFormat,
|
||||
time.RFC850,
|
||||
time.ANSIC,
|
||||
}
|
||||
|
||||
// ParseTime parses a time header (such as the Date: header),
|
||||
// trying each of the three formats allowed by HTTP/1.1:
|
||||
// TimeFormat, time.RFC850, and time.ANSIC.
|
||||
func ParseTime(text string) (t time.Time, err error) {
|
||||
for _, layout := range timeFormats {
|
||||
t, err = time.Parse(layout, text)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ")
|
||||
|
||||
// stringWriter implements WriteString on a Writer.
|
||||
type stringWriter struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (w stringWriter) WriteString(s string) (n int, err error) {
|
||||
return w.w.Write([]byte(s))
|
||||
}
|
||||
|
||||
type keyValues struct {
|
||||
key string
|
||||
values []string
|
||||
}
|
||||
|
||||
// A headerSorter implements sort.Interface by sorting a []keyValues
|
||||
// by key. It's used as a pointer, so it can fit in a sort.Interface
|
||||
// interface value without allocation.
|
||||
type headerSorter struct {
|
||||
kvs []keyValues
|
||||
}
|
||||
|
||||
func (s *headerSorter) Len() int { return len(s.kvs) }
|
||||
func (s *headerSorter) Swap(i, j int) { s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i] }
|
||||
func (s *headerSorter) Less(i, j int) bool { return s.kvs[i].key < s.kvs[j].key }
|
||||
|
||||
var headerSorterPool = sync.Pool{
|
||||
New: func() interface{} { return new(headerSorter) },
|
||||
}
|
||||
|
||||
// sortedKeyValues returns h's keys sorted in the returned kvs
|
||||
// slice. The headerSorter used to sort is also returned, for possible
|
||||
// return to headerSorterCache.
|
||||
func (h Header) sortedKeyValues(exclude map[string]bool) (kvs []keyValues, hs *headerSorter) {
|
||||
hs = headerSorterPool.Get().(*headerSorter)
|
||||
if cap(hs.kvs) < len(h) {
|
||||
hs.kvs = make([]keyValues, 0, len(h))
|
||||
}
|
||||
kvs = hs.kvs[:0]
|
||||
for k, vv := range h {
|
||||
if !exclude[k] {
|
||||
kvs = append(kvs, keyValues{k, vv})
|
||||
}
|
||||
}
|
||||
hs.kvs = kvs
|
||||
sort.Sort(hs)
|
||||
return kvs, hs
|
||||
}
|
||||
|
||||
// WriteSubset writes a header in wire format.
|
||||
// If exclude is not nil, keys where exclude[key] == true are not written.
|
||||
// Keys are not canonicalized before checking the exclude map.
|
||||
func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
|
||||
return h.writeSubset(w, exclude, nil)
|
||||
}
|
||||
|
||||
func (h Header) writeSubset(w io.Writer, exclude map[string]bool, trace *httptrace.ClientTrace) error {
|
||||
ws, ok := w.(io.StringWriter)
|
||||
if !ok {
|
||||
ws = stringWriter{w}
|
||||
}
|
||||
kvs, sorter := h.sortedKeyValues(exclude)
|
||||
var formattedVals []string
|
||||
for _, kv := range kvs {
|
||||
for _, v := range kv.values {
|
||||
v = headerNewlineToSpace.Replace(v)
|
||||
v = textproto.TrimString(v)
|
||||
for _, s := range []string{kv.key, ": ", v, "\r\n"} {
|
||||
if _, err := ws.WriteString(s); err != nil {
|
||||
headerSorterPool.Put(sorter)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if trace != nil && trace.WroteHeaderField != nil {
|
||||
formattedVals = append(formattedVals, v)
|
||||
}
|
||||
}
|
||||
if trace != nil && trace.WroteHeaderField != nil {
|
||||
trace.WroteHeaderField(kv.key, formattedVals)
|
||||
formattedVals = nil
|
||||
}
|
||||
}
|
||||
headerSorterPool.Put(sorter)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanonicalHeaderKey returns the canonical format of the
|
||||
// header key s. The canonicalization converts the first
|
||||
// letter and any letter following a hyphen to upper case;
|
||||
// the rest are converted to lowercase. For example, the
|
||||
// canonical key for "accept-encoding" is "Accept-Encoding".
|
||||
// If s contains a space or invalid header field bytes, it is
|
||||
// returned without modifications.
|
||||
func CanonicalHeaderKey(s string) string { return textproto.CanonicalMIMEHeaderKey(s) }
|
||||
|
||||
// hasToken reports whether token appears with v, ASCII
|
||||
// case-insensitive, with space or comma boundaries.
|
||||
// token must be all lowercase.
|
||||
// v may contain mixed cased.
|
||||
func hasToken(v, token string) bool {
|
||||
if len(token) > len(v) || token == "" {
|
||||
return false
|
||||
}
|
||||
if v == token {
|
||||
return true
|
||||
}
|
||||
for sp := 0; sp <= len(v)-len(token); sp++ {
|
||||
// Check that first character is good.
|
||||
// The token is ASCII, so checking only a single byte
|
||||
// is sufficient. We skip this potential starting
|
||||
// position if both the first byte and its potential
|
||||
// ASCII uppercase equivalent (b|0x20) don't match.
|
||||
// False positives ('^' => '~') are caught by EqualFold.
|
||||
if b := v[sp]; b != token[0] && b|0x20 != token[0] {
|
||||
continue
|
||||
}
|
||||
// Check that start pos is on a valid token boundary.
|
||||
if sp > 0 && !isTokenBoundary(v[sp-1]) {
|
||||
continue
|
||||
}
|
||||
// Check that end pos is on a valid token boundary.
|
||||
if endPos := sp + len(token); endPos != len(v) && !isTokenBoundary(v[endPos]) {
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(v[sp:sp+len(token)], token) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isTokenBoundary(b byte) bool {
|
||||
return b == ' ' || b == ',' || b == '\t'
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// incomparable is a zero-width, non-comparable type. Adding it to a struct
|
||||
// makes that struct also non-comparable, and generally doesn't add
|
||||
// any size (as long as it's first).
|
||||
type incomparable [0]func()
|
||||
|
||||
// maxInt64 is the effective "infinite" value for the Server and
|
||||
// Transport's byte-limiting readers.
|
||||
const maxInt64 = 1<<63 - 1
|
||||
|
||||
// aLongTimeAgo is a non-zero time, far in the past, used for
|
||||
// immediate cancellation of network operations.
|
||||
var aLongTimeAgo = time.Unix(1, 0)
|
||||
|
||||
// omitBundledHTTP2 is set by omithttp2.go when the nethttpomithttp2
|
||||
// build tag is set. That means h2_bundle.go isn't compiled in and we
|
||||
// shouldn't try to use it.
|
||||
var omitBundledHTTP2 bool
|
||||
|
||||
// TODO(bradfitz): move common stuff here. The other files have accumulated
|
||||
// generic http stuff in random places.
|
||||
|
||||
// contextKey is a value for use with context.WithValue. It's used as
|
||||
// a pointer so it fits in an interface{} without allocation.
|
||||
type contextKey struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (k *contextKey) String() string { return "net/http context value " + k.name }
|
||||
|
||||
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
|
||||
// return true if the string includes a port.
|
||||
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
|
||||
|
||||
// removeEmptyPort strips the empty port in ":port" to ""
|
||||
// as mandated by RFC 3986 Section 6.2.3.
|
||||
func removeEmptyPort(host string) string {
|
||||
if hasPort(host) {
|
||||
return strings.TrimSuffix(host, ":")
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func isNotToken(r rune) bool {
|
||||
return !httpguts.IsTokenRune(r)
|
||||
}
|
||||
|
||||
func isASCII(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= utf8.RuneSelf {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// stringContainsCTLByte reports whether s contains any ASCII control character.
|
||||
func stringContainsCTLByte(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
if b < ' ' || b == 0x7f {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hexEscapeNonASCII(s string) string {
|
||||
newLen := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= utf8.RuneSelf {
|
||||
newLen += 3
|
||||
} else {
|
||||
newLen++
|
||||
}
|
||||
}
|
||||
if newLen == len(s) {
|
||||
return s
|
||||
}
|
||||
b := make([]byte, 0, newLen)
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= utf8.RuneSelf {
|
||||
b = append(b, '%')
|
||||
b = strconv.AppendInt(b, int64(s[i]), 16)
|
||||
} else {
|
||||
b = append(b, s[i])
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// NoBody is an io.ReadCloser with no bytes. Read always returns EOF
|
||||
// and Close always returns nil. It can be used in an outgoing client
|
||||
// request to explicitly signal that a request has zero bytes.
|
||||
// An alternative, however, is to simply set Request.Body to nil.
|
||||
var NoBody = noBody{}
|
||||
|
||||
type noBody struct{}
|
||||
|
||||
func (noBody) Read([]byte) (int, error) { return 0, io.EOF }
|
||||
func (noBody) Close() error { return nil }
|
||||
func (noBody) WriteTo(io.Writer) (int64, error) { return 0, nil }
|
||||
|
||||
var (
|
||||
// verify that an io.Copy from NoBody won't require a buffer:
|
||||
_ io.WriterTo = NoBody
|
||||
_ io.ReadCloser = NoBody
|
||||
)
|
||||
|
||||
// PushOptions describes options for Pusher.Push.
|
||||
type PushOptions struct {
|
||||
// Method specifies the HTTP method for the promised request.
|
||||
// If set, it must be "GET" or "HEAD". Empty means "GET".
|
||||
Method string
|
||||
|
||||
// Header specifies additional promised request headers. This cannot
|
||||
// include HTTP/2 pseudo header fields like ":path" and ":scheme",
|
||||
// which will be added automatically.
|
||||
Header Header
|
||||
}
|
||||
|
||||
// Pusher is the interface implemented by ResponseWriters that support
|
||||
// HTTP/2 server push. For more background, see
|
||||
// https://tools.ietf.org/html/rfc7540#section-8.2.
|
||||
type Pusher interface {
|
||||
// Push initiates an HTTP/2 server push. This constructs a synthetic
|
||||
// request using the given target and options, serializes that request
|
||||
// into a PUSH_PROMISE frame, then dispatches that request using the
|
||||
// server's request handler. If opts is nil, default options are used.
|
||||
//
|
||||
// The target must either be an absolute path (like "/path") or an absolute
|
||||
// URL that contains a valid host and the same scheme as the parent request.
|
||||
// If the target is a path, it will inherit the scheme and host of the
|
||||
// parent request.
|
||||
//
|
||||
// The HTTP/2 spec disallows recursive pushes and cross-authority pushes.
|
||||
// Push may or may not detect these invalid pushes; however, invalid
|
||||
// pushes will be detected and canceled by conforming clients.
|
||||
//
|
||||
// Handlers that wish to push URL X should call Push before sending any
|
||||
// data that may trigger a request for URL X. This avoids a race where the
|
||||
// client issues requests for X before receiving the PUSH_PROMISE for X.
|
||||
//
|
||||
// Push will run in a separate goroutine making the order of arrival
|
||||
// non-deterministic. Any required synchronization needs to be implemented
|
||||
// by the caller.
|
||||
//
|
||||
// Push returns ErrNotSupported if the client has disabled push or if push
|
||||
// is not supported on the underlying connection.
|
||||
Push(target string, opts *PushOptions) error
|
||||
}
|
||||
@@ -0,0 +1,608 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func badStringError(what, val string) error { return fmt.Errorf("%s %q", what, val) }
|
||||
|
||||
type Request struct {
|
||||
// Method specifies the HTTP method (GET, POST, PUT, etc.).
|
||||
// For client requests, an empty string means GET.
|
||||
//
|
||||
// Go's HTTP client does not support sending a request with
|
||||
// the CONNECT method. See the documentation on Transport for
|
||||
// details.
|
||||
Method string
|
||||
|
||||
// URL specifies either the URI being requested (for server
|
||||
// requests) or the URL to access (for client requests).
|
||||
//
|
||||
// For server requests, the URL is parsed from the URI
|
||||
// supplied on the Request-Line as stored in RequestURI. For
|
||||
// most requests, fields other than Path and RawQuery will be
|
||||
// empty. (See RFC 7230, Section 5.3)
|
||||
//
|
||||
// For client requests, the URL's Host specifies the server to
|
||||
// connect to, while the Request's Host field optionally
|
||||
// specifies the Host header value to send in the HTTP
|
||||
// request.
|
||||
URL *url.URL
|
||||
|
||||
// The protocol version for incoming server requests.
|
||||
//
|
||||
// For client requests, these fields are ignored. The HTTP
|
||||
// client code always uses either HTTP/1.1 or HTTP/2.
|
||||
// See the docs on Transport for details.
|
||||
Proto string // "HTTP/1.0"
|
||||
ProtoMajor int // 1
|
||||
ProtoMinor int // 0
|
||||
|
||||
// Header contains the request header fields either received
|
||||
// by the server or to be sent by the client.
|
||||
//
|
||||
// If a server received a request with header lines,
|
||||
//
|
||||
// Host: example.com
|
||||
// accept-encoding: gzip, deflate
|
||||
// Accept-Language: en-us
|
||||
// fOO: Bar
|
||||
// foo: two
|
||||
//
|
||||
// then
|
||||
//
|
||||
// Header = map[string][]string{
|
||||
// "Accept-Encoding": {"gzip, deflate"},
|
||||
// "Accept-Language": {"en-us"},
|
||||
// "Foo": {"Bar", "two"},
|
||||
// }
|
||||
//
|
||||
// For incoming requests, the Host header is promoted to the
|
||||
// Request.Host field and removed from the Header map.
|
||||
//
|
||||
// HTTP defines that header names are case-insensitive. The
|
||||
// request parser implements this by using CanonicalHeaderKey,
|
||||
// making the first character and any characters following a
|
||||
// hyphen uppercase and the rest lowercase.
|
||||
//
|
||||
// For client requests, certain headers such as Content-Length
|
||||
// and Connection are automatically written when needed and
|
||||
// values in Header may be ignored. See the documentation
|
||||
// for the Request.Write method.
|
||||
Header Header
|
||||
|
||||
// Body is the request's body.
|
||||
//
|
||||
// For client requests, a nil body means the request has no
|
||||
// body, such as a GET request. The HTTP Client's Transport
|
||||
// is responsible for calling the Close method.
|
||||
//
|
||||
// For server requests, the Request Body is always non-nil
|
||||
// but will return EOF immediately when no body is present.
|
||||
// The Server will close the request body. The ServeHTTP
|
||||
// Handler does not need to.
|
||||
//
|
||||
// Body must allow Read to be called concurrently with Close.
|
||||
// In particular, calling Close should unblock a Read waiting
|
||||
// for input.
|
||||
Body io.ReadCloser
|
||||
|
||||
// GetBody defines an optional func to return a new copy of
|
||||
// Body. It is used for client requests when a redirect requires
|
||||
// reading the body more than once. Use of GetBody still
|
||||
// requires setting Body.
|
||||
//
|
||||
// For server requests, it is unused.
|
||||
GetBody func() (io.ReadCloser, error)
|
||||
|
||||
// ContentLength records the length of the associated content.
|
||||
// The value -1 indicates that the length is unknown.
|
||||
// Values >= 0 indicate that the given number of bytes may
|
||||
// be read from Body.
|
||||
//
|
||||
// For client requests, a value of 0 with a non-nil Body is
|
||||
// also treated as unknown.
|
||||
ContentLength int64
|
||||
|
||||
// TransferEncoding lists the transfer encodings from outermost to
|
||||
// innermost. An empty list denotes the "identity" encoding.
|
||||
// TransferEncoding can usually be ignored; chunked encoding is
|
||||
// automatically added and removed as necessary when sending and
|
||||
// receiving requests.
|
||||
TransferEncoding []string
|
||||
|
||||
// Close indicates whether to close the connection after
|
||||
// replying to this request (for servers) or after sending this
|
||||
// request and reading its response (for clients).
|
||||
//
|
||||
// For server requests, the HTTP server handles this automatically
|
||||
// and this field is not needed by Handlers.
|
||||
//
|
||||
// For client requests, setting this field prevents re-use of
|
||||
// TCP connections between requests to the same hosts, as if
|
||||
// Transport.DisableKeepAlives were set.
|
||||
Close bool
|
||||
|
||||
// For server requests, Host specifies the host on which the
|
||||
// URL is sought. For HTTP/1 (per RFC 7230, section 5.4), this
|
||||
// is either the value of the "Host" header or the host name
|
||||
// given in the URL itself. For HTTP/2, it is the value of the
|
||||
// ":authority" pseudo-header field.
|
||||
// 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
|
||||
|
||||
// Form contains the parsed form data, including both the URL
|
||||
// field's query parameters and the PATCH, POST, or PUT form data.
|
||||
// This field is only available after ParseForm is called.
|
||||
// The HTTP client ignores Form and uses Body instead.
|
||||
Form url.Values
|
||||
|
||||
// PostForm contains the parsed form data from PATCH, POST
|
||||
// or PUT body parameters.
|
||||
//
|
||||
// This field is only available after ParseForm is called.
|
||||
// The HTTP client ignores PostForm and uses Body instead.
|
||||
PostForm url.Values
|
||||
|
||||
// MultipartForm is the parsed multipart form, including file uploads.
|
||||
// This field is only available after ParseMultipartForm is called.
|
||||
// The HTTP client ignores MultipartForm and uses Body instead.
|
||||
MultipartForm *multipart.Form
|
||||
|
||||
// Trailer specifies additional headers that are sent after the request
|
||||
// body.
|
||||
//
|
||||
// For server requests, the Trailer map initially contains only the
|
||||
// trailer keys, with nil values. (The client declares which trailers it
|
||||
// will later send.) While the handler is reading from Body, it must
|
||||
// not reference Trailer. After reading from Body returns EOF, Trailer
|
||||
// can be read again and will contain non-nil values, if they were sent
|
||||
// by the client.
|
||||
//
|
||||
// For client requests, Trailer must be initialized to a map containing
|
||||
// the trailer keys to later send. The values may be nil or their final
|
||||
// values. The ContentLength must be 0 or -1, to send a chunked request.
|
||||
// After the HTTP request is sent the map values can be updated while
|
||||
// the request body is read. Once the body returns EOF, the caller must
|
||||
// not mutate Trailer.
|
||||
//
|
||||
// Few HTTP clients, servers, or proxies support HTTP trailers.
|
||||
Trailer Header
|
||||
|
||||
// RemoteAddr allows HTTP servers and other software to record
|
||||
// the network address that sent the request, usually for
|
||||
// logging. This field is not filled in by ReadRequest and
|
||||
// has no defined format. The HTTP server in this package
|
||||
// sets RemoteAddr to an "IP:port" address before invoking a
|
||||
// handler.
|
||||
// This field is ignored by the HTTP client.
|
||||
RemoteAddr string
|
||||
|
||||
// RequestURI is the unmodified request-target of the
|
||||
// Request-Line (RFC 7230, Section 3.1.1) as sent by the client
|
||||
// to a server. Usually the URL field should be used instead.
|
||||
// It is an error to set this field in an HTTP client request.
|
||||
RequestURI string
|
||||
|
||||
// TLS allows HTTP servers and other software to record
|
||||
// information about the TLS connection on which the request
|
||||
// was received. This field is not filled in by ReadRequest.
|
||||
// The HTTP server in this package sets the field for
|
||||
// TLS-enabled connections before invoking a handler;
|
||||
// otherwise it leaves the field nil.
|
||||
// This field is ignored by the HTTP client.
|
||||
TLS *tls.ConnectionState
|
||||
|
||||
// Cancel is an optional channel whose closure indicates that the client
|
||||
// request should be regarded as canceled. Not all implementations of
|
||||
// RoundTripper may support Cancel.
|
||||
//
|
||||
// For server requests, this field is not applicable.
|
||||
//
|
||||
// Deprecated: Set the Request's context with NewRequestWithContext
|
||||
// instead. If a Request's Cancel field and context are both
|
||||
// set, it is undefined whether Cancel is respected.
|
||||
Cancel <-chan struct{}
|
||||
|
||||
// Response is the redirect response which caused this request
|
||||
// to be created. This field is only populated during client
|
||||
// redirects.
|
||||
Response *Response
|
||||
|
||||
// ctx is either the client or server context. It should only
|
||||
// be modified via copying the whole Request using WithContext.
|
||||
// It is unexported to prevent people from using Context wrong
|
||||
// and mutating the contexts held by callers of the same request.
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// ProtoAtLeast reports whether the HTTP protocol used
|
||||
// in the request is at least major.minor.
|
||||
func (r *Request) ProtoAtLeast(major, minor int) bool {
|
||||
return r.ProtoMajor > major ||
|
||||
r.ProtoMajor == major && r.ProtoMinor >= minor
|
||||
}
|
||||
|
||||
// isH2Upgrade reports whether r represents the http2 "client preface"
|
||||
// magic string.
|
||||
func (r *Request) isH2Upgrade() bool {
|
||||
return r.Method == "PRI" && len(r.Header) == 0 && r.URL.Path == "*" && r.Proto == "HTTP/2.0"
|
||||
}
|
||||
|
||||
// ParseHTTPVersion parses an HTTP version string.
|
||||
// "HTTP/1.0" returns (1, 0, true).
|
||||
func ParseHTTPVersion(vers string) (major, minor int, ok bool) {
|
||||
const Big = 1000000 // arbitrary upper bound
|
||||
switch vers {
|
||||
case "HTTP/1.1":
|
||||
return 1, 1, true
|
||||
case "HTTP/1.0":
|
||||
return 1, 0, true
|
||||
}
|
||||
if !strings.HasPrefix(vers, "HTTP/") {
|
||||
return 0, 0, false
|
||||
}
|
||||
dot := strings.Index(vers, ".")
|
||||
if dot < 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
major, err := strconv.Atoi(vers[5:dot])
|
||||
if err != nil || major < 0 || major > Big {
|
||||
return 0, 0, false
|
||||
}
|
||||
minor, err = strconv.Atoi(vers[dot+1:])
|
||||
if err != nil || minor < 0 || minor > Big {
|
||||
return 0, 0, false
|
||||
}
|
||||
return major, minor, true
|
||||
}
|
||||
|
||||
func validMethod(method string) bool {
|
||||
/*
|
||||
Method = "OPTIONS" ; Section 9.2
|
||||
| "GET" ; Section 9.3
|
||||
| "HEAD" ; Section 9.4
|
||||
| "POST" ; Section 9.5
|
||||
| "PUT" ; Section 9.6
|
||||
| "DELETE" ; Section 9.7
|
||||
| "TRACE" ; Section 9.8
|
||||
| "CONNECT" ; Section 9.9
|
||||
| extension-method
|
||||
extension-method = token
|
||||
token = 1*<any CHAR except CTLs or separators>
|
||||
*/
|
||||
return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1
|
||||
}
|
||||
|
||||
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
|
||||
func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
|
||||
s1 := strings.Index(line, " ")
|
||||
s2 := strings.Index(line[s1+1:], " ")
|
||||
if s1 < 0 || s2 < 0 {
|
||||
return
|
||||
}
|
||||
s2 += s1 + 1
|
||||
return line[:s1], line[s1+1 : s2], line[s2+1:], true
|
||||
}
|
||||
|
||||
var textprotoReaderPool sync.Pool
|
||||
|
||||
func newTextprotoReader(br *bufio.Reader) *textproto.Reader {
|
||||
if v := textprotoReaderPool.Get(); v != nil {
|
||||
tr := v.(*textproto.Reader)
|
||||
tr.R = br
|
||||
return tr
|
||||
}
|
||||
return textproto.NewReader(br)
|
||||
}
|
||||
|
||||
func putTextprotoReader(r *textproto.Reader) {
|
||||
r.R = nil
|
||||
textprotoReaderPool.Put(r)
|
||||
}
|
||||
|
||||
// ReadRequest reads and parses an incoming request from b.
|
||||
//
|
||||
// ReadRequest is a low-level function and should only be used for
|
||||
// specialized applications; most code should use the Server to read
|
||||
// requests and handle them via the Handler interface. ReadRequest
|
||||
// only supports HTTP/1.x requests. For HTTP/2, use golang.org/x/net/http2.
|
||||
func ReadRequest(b *bufio.Reader) (*Request, error) {
|
||||
return readRequest(b, deleteHostHeader)
|
||||
}
|
||||
|
||||
// Constants for readRequest's deleteHostHeader parameter.
|
||||
const (
|
||||
deleteHostHeader = true
|
||||
keepHostHeader = false
|
||||
)
|
||||
|
||||
func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error) {
|
||||
tp := newTextprotoReader(b)
|
||||
req = new(Request)
|
||||
|
||||
// First line: GET /index.html HTTP/1.0
|
||||
var s string
|
||||
if s, err = tp.ReadLine(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
putTextprotoReader(tp)
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
}()
|
||||
|
||||
var ok bool
|
||||
req.Method, req.RequestURI, req.Proto, ok = parseRequestLine(s)
|
||||
if !ok {
|
||||
return nil, badStringError("malformed HTTP request", s)
|
||||
}
|
||||
if !validMethod(req.Method) {
|
||||
return nil, badStringError("invalid method", req.Method)
|
||||
}
|
||||
rawurl := req.RequestURI
|
||||
if req.ProtoMajor, req.ProtoMinor, ok = ParseHTTPVersion(req.Proto); !ok {
|
||||
return nil, badStringError("malformed HTTP version", req.Proto)
|
||||
}
|
||||
|
||||
// CONNECT requests are used two different ways, and neither uses a full URL:
|
||||
// The standard use is to tunnel HTTPS through an HTTP proxy.
|
||||
// It looks like "CONNECT www.google.com:443 HTTP/1.1", and the parameter is
|
||||
// just the authority section of a URL. This information should go in req.URL.Host.
|
||||
//
|
||||
// The net/rpc package also uses CONNECT, but there the parameter is a path
|
||||
// that starts with a slash. It can be parsed with the regular URL parser,
|
||||
// and the path will end up in req.URL.Path, where it needs to be in order for
|
||||
// RPC to work.
|
||||
justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/")
|
||||
if justAuthority {
|
||||
rawurl = "http://" + rawurl
|
||||
}
|
||||
|
||||
if req.URL, err = url.ParseRequestURI(rawurl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if justAuthority {
|
||||
// Strip the bogus "http://" back off.
|
||||
req.URL.Scheme = ""
|
||||
}
|
||||
|
||||
// Subsequent lines: Key: value.
|
||||
mimeHeader, err := tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header = Header(mimeHeader)
|
||||
|
||||
// RFC 7230, section 5.3: Must treat
|
||||
// GET /index.html HTTP/1.1
|
||||
// Host: www.google.com
|
||||
// and
|
||||
// GET http://www.google.com/index.html HTTP/1.1
|
||||
// Host: doesntmatter
|
||||
// the same. In the second case, any Host line is ignored.
|
||||
req.Host = req.URL.Host
|
||||
if req.Host == "" {
|
||||
req.Host = req.Header.get("Host")
|
||||
}
|
||||
if deleteHostHeader {
|
||||
delete(req.Header, "Host")
|
||||
}
|
||||
|
||||
fixPragmaCacheControl(req.Header)
|
||||
|
||||
req.Close = shouldClose(req.ProtoMajor, req.ProtoMinor, req.Header, false)
|
||||
|
||||
err = readTransfer(req, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.isH2Upgrade() {
|
||||
// Because it's neither chunked, nor declared:
|
||||
req.ContentLength = -1
|
||||
|
||||
// We want to give handlers a chance to hijack the
|
||||
// connection, but we need to prevent the Server from
|
||||
// dealing with the connection further if it's not
|
||||
// hijacked. Set Close to ensure that:
|
||||
req.Close = true
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// MaxBytesReader is similar to io.LimitReader but is intended for
|
||||
// limiting the size of incoming request bodies. In contrast to
|
||||
// io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a
|
||||
// non-EOF error for a Read beyond the limit, and closes the
|
||||
// underlying reader when its Close method is called.
|
||||
//
|
||||
// MaxBytesReader prevents clients from accidentally or maliciously
|
||||
// sending a large request and wasting server resources.
|
||||
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser {
|
||||
return &maxBytesReader{w: w, r: r, n: n}
|
||||
}
|
||||
|
||||
type maxBytesReader struct {
|
||||
w ResponseWriter
|
||||
r io.ReadCloser // underlying reader
|
||||
n int64 // max bytes remaining
|
||||
err error // sticky error
|
||||
}
|
||||
|
||||
func (l *maxBytesReader) Read(p []byte) (n int, err error) {
|
||||
if l.err != nil {
|
||||
return 0, l.err
|
||||
}
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
// If they asked for a 32KB byte read but only 5 bytes are
|
||||
// remaining, no need to read 32KB. 6 bytes will answer the
|
||||
// question of the whether we hit the limit or go past it.
|
||||
if int64(len(p)) > l.n+1 {
|
||||
p = p[:l.n+1]
|
||||
}
|
||||
n, err = l.r.Read(p)
|
||||
|
||||
if int64(n) <= l.n {
|
||||
l.n -= int64(n)
|
||||
l.err = err
|
||||
return n, err
|
||||
}
|
||||
|
||||
n = int(l.n)
|
||||
l.n = 0
|
||||
|
||||
// The server code and client code both use
|
||||
// maxBytesReader. This "requestTooLarge" check is
|
||||
// only used by the server code. To prevent binaries
|
||||
// which only using the HTTP Client code (such as
|
||||
// cmd/go) from also linking in the HTTP server, don't
|
||||
// use a static type assertion to the server
|
||||
// "*response" type. Check this interface instead:
|
||||
type requestTooLarger interface {
|
||||
requestTooLarge()
|
||||
}
|
||||
if res, ok := l.w.(requestTooLarger); ok {
|
||||
res.requestTooLarge()
|
||||
}
|
||||
l.err = errors.New("http: request body too large")
|
||||
return n, l.err
|
||||
}
|
||||
|
||||
func (l *maxBytesReader) Close() error {
|
||||
return l.r.Close()
|
||||
}
|
||||
|
||||
func copyValues(dst, src url.Values) {
|
||||
for k, vs := range src {
|
||||
dst[k] = append(dst[k], vs...)
|
||||
}
|
||||
}
|
||||
|
||||
func parsePostForm(r *Request) (vs url.Values, err error) {
|
||||
if r.Body == nil {
|
||||
err = errors.New("missing form body")
|
||||
return
|
||||
}
|
||||
ct := r.Header.Get("Content-Type")
|
||||
// RFC 7231, section 3.1.1.5 - empty type
|
||||
// MAY be treated as application/octet-stream
|
||||
if ct == "" {
|
||||
ct = "application/octet-stream"
|
||||
}
|
||||
ct, _, err = mime.ParseMediaType(ct)
|
||||
switch {
|
||||
case ct == "application/x-www-form-urlencoded":
|
||||
var reader io.Reader = r.Body
|
||||
maxFormSize := int64(1<<63 - 1)
|
||||
if _, ok := r.Body.(*maxBytesReader); !ok {
|
||||
maxFormSize = int64(10 << 20) // 10 MB is a lot of text.
|
||||
reader = io.LimitReader(r.Body, maxFormSize+1)
|
||||
}
|
||||
b, e := io.ReadAll(reader)
|
||||
if e != nil {
|
||||
if err == nil {
|
||||
err = e
|
||||
}
|
||||
break
|
||||
}
|
||||
if int64(len(b)) > maxFormSize {
|
||||
err = errors.New("http: POST too large")
|
||||
return
|
||||
}
|
||||
vs, e = url.ParseQuery(string(b))
|
||||
if err == nil {
|
||||
err = e
|
||||
}
|
||||
case ct == "multipart/form-data":
|
||||
// handled by ParseMultipartForm (which is calling us, or should be)
|
||||
// TODO(bradfitz): there are too many possible
|
||||
// orders to call too many functions here.
|
||||
// Clean this up and write more tests.
|
||||
// request_test.go contains the start of this,
|
||||
// in TestParseMultipartFormOrder and others.
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseForm populates r.Form and r.PostForm.
|
||||
//
|
||||
// For all requests, ParseForm parses the raw query from the URL and updates
|
||||
// r.Form.
|
||||
//
|
||||
// For POST, PUT, and PATCH requests, it also reads the request body, parses it
|
||||
// as a form and puts the results into both r.PostForm and r.Form. Request body
|
||||
// parameters take precedence over URL query string values in r.Form.
|
||||
//
|
||||
// If the request Body's size has not already been limited by MaxBytesReader,
|
||||
// the size is capped at 10MB.
|
||||
//
|
||||
// For other HTTP methods, or when the Content-Type is not
|
||||
// application/x-www-form-urlencoded, the request Body is not read, and
|
||||
// r.PostForm is initialized to a non-nil, empty value.
|
||||
//
|
||||
// ParseMultipartForm calls ParseForm automatically.
|
||||
// ParseForm is idempotent.
|
||||
func (r *Request) ParseForm() error {
|
||||
var err error
|
||||
if r.PostForm == nil {
|
||||
if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
|
||||
r.PostForm, err = parsePostForm(r)
|
||||
}
|
||||
if r.PostForm == nil {
|
||||
r.PostForm = make(url.Values)
|
||||
}
|
||||
}
|
||||
if r.Form == nil {
|
||||
if len(r.PostForm) > 0 {
|
||||
r.Form = make(url.Values)
|
||||
copyValues(r.Form, r.PostForm)
|
||||
}
|
||||
var newValues url.Values
|
||||
if r.URL != nil {
|
||||
var e error
|
||||
newValues, e = url.ParseQuery(r.URL.RawQuery)
|
||||
if err == nil {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
if newValues == nil {
|
||||
newValues = make(url.Values)
|
||||
}
|
||||
if r.Form == nil {
|
||||
r.Form = newValues
|
||||
} else {
|
||||
copyValues(r.Form, newValues)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Response represents the response from an HTTP request.
|
||||
//
|
||||
// The Client and Transport return Responses from servers once
|
||||
// the response headers have been received. The response body
|
||||
// is streamed on demand as the Body field is read.
|
||||
type Response struct {
|
||||
Status string // e.g. "200 OK"
|
||||
StatusCode int // e.g. 200
|
||||
Proto string // e.g. "HTTP/1.0"
|
||||
ProtoMajor int // e.g. 1
|
||||
ProtoMinor int // e.g. 0
|
||||
|
||||
// Header maps header keys to values. If the response had multiple
|
||||
// headers with the same key, they may be concatenated, with comma
|
||||
// delimiters. (RFC 7230, section 3.2.2 requires that multiple headers
|
||||
// be semantically equivalent to a comma-delimited sequence.) When
|
||||
// Header values are duplicated by other fields in this struct (e.g.,
|
||||
// ContentLength, TransferEncoding, Trailer), the field values are
|
||||
// authoritative.
|
||||
//
|
||||
// Keys in the map are canonicalized (see CanonicalHeaderKey).
|
||||
Header Header
|
||||
|
||||
// Body represents the response body.
|
||||
//
|
||||
// The response body is streamed on demand as the Body field
|
||||
// is read. If the network connection fails or the server
|
||||
// terminates the response, Body.Read calls return an error.
|
||||
//
|
||||
// The http Client and Transport guarantee that Body is always
|
||||
// non-nil, even on responses without a body or responses with
|
||||
// a zero-length body. It is the caller's responsibility to
|
||||
// close Body. The default HTTP client's Transport may not
|
||||
// reuse HTTP/1.x "keep-alive" TCP connections if the Body is
|
||||
// not read to completion and closed.
|
||||
//
|
||||
// The Body is automatically dechunked if the server replied
|
||||
// with a "chunked" Transfer-Encoding.
|
||||
//
|
||||
// As of Go 1.12, the Body will also implement io.Writer
|
||||
// on a successful "101 Switching Protocols" response,
|
||||
// as used by WebSockets and HTTP/2's "h2c" mode.
|
||||
Body io.ReadCloser
|
||||
|
||||
// ContentLength records the length of the associated content. The
|
||||
// value -1 indicates that the length is unknown. Unless Request.Method
|
||||
// is "HEAD", values >= 0 indicate that the given number of bytes may
|
||||
// be read from Body.
|
||||
ContentLength int64
|
||||
|
||||
// Contains transfer encodings from outer-most to inner-most. Value is
|
||||
// nil, means that "identity" encoding is used.
|
||||
TransferEncoding []string
|
||||
|
||||
// Close records whether the header directed that the connection be
|
||||
// closed after reading Body. The value is advice for clients: neither
|
||||
// ReadResponse nor Response.Write ever closes a connection.
|
||||
Close bool
|
||||
|
||||
// Uncompressed reports whether the response was sent compressed but
|
||||
// was decompressed by the http package. When true, reading from
|
||||
// Body yields the uncompressed content instead of the compressed
|
||||
// content actually set from the server, ContentLength is set to -1,
|
||||
// and the "Content-Length" and "Content-Encoding" fields are deleted
|
||||
// from the responseHeader. To get the original response from
|
||||
// the server, set Transport.DisableCompression to true.
|
||||
Uncompressed bool
|
||||
|
||||
// Trailer maps trailer keys to values in the same
|
||||
// format as Header.
|
||||
//
|
||||
// The Trailer initially contains only nil values, one for
|
||||
// each key specified in the server's "Trailer" header
|
||||
// value. Those values are not added to Header.
|
||||
//
|
||||
// Trailer must not be accessed concurrently with Read calls
|
||||
// on the Body.
|
||||
//
|
||||
// After Body.Read has returned io.EOF, Trailer will contain
|
||||
// any trailer values sent by the server.
|
||||
Trailer Header
|
||||
|
||||
// Request is the request that was sent to obtain this Response.
|
||||
// Request's Body is nil (having already been consumed).
|
||||
// This is only populated for Client requests.
|
||||
Request *Request
|
||||
|
||||
// TLS contains information about the TLS connection on which the
|
||||
// response was received. It is nil for unencrypted responses.
|
||||
// The pointer is shared between responses and should not be
|
||||
// modified.
|
||||
TLS *tls.ConnectionState
|
||||
}
|
||||
|
||||
// RFC 7234, section 5.4: Should treat
|
||||
// Pragma: no-cache
|
||||
// like
|
||||
// Cache-Control: no-cache
|
||||
func fixPragmaCacheControl(header Header) {
|
||||
if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" {
|
||||
if _, presentcc := header["Cache-Control"]; !presentcc {
|
||||
header["Cache-Control"] = []string{"no-cache"}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
urlpkg "net/url"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A Handler responds to an HTTP request.
|
||||
//
|
||||
// ServeHTTP should write reply headers and data to the ResponseWriter
|
||||
// and then return. Returning signals that the request is finished; it
|
||||
// is not valid to use the ResponseWriter or read from the
|
||||
// Request.Body after or concurrently with the completion of the
|
||||
// ServeHTTP call.
|
||||
//
|
||||
// Depending on the HTTP client software, HTTP protocol version, and
|
||||
// any intermediaries between the client and the Go server, it may not
|
||||
// be possible to read from the Request.Body after writing to the
|
||||
// ResponseWriter. Cautious handlers should read the Request.Body
|
||||
// first, and then reply.
|
||||
//
|
||||
// Except for reading the body, handlers should not modify the
|
||||
// provided Request.
|
||||
//
|
||||
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
|
||||
// that the effect of the panic was isolated to the active request.
|
||||
// It recovers the panic, logs a stack trace to the server error log,
|
||||
// and either closes the network connection or sends an HTTP/2
|
||||
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
|
||||
// the client sees an interrupted response but the server doesn't log
|
||||
// an error, panic with the value ErrAbortHandler.
|
||||
type Handler interface {
|
||||
ServeHTTP(ResponseWriter, *Request)
|
||||
}
|
||||
|
||||
// A ResponseWriter interface is used by an HTTP handler to
|
||||
// construct an HTTP response.
|
||||
//
|
||||
// A ResponseWriter may not be used after the Handler.ServeHTTP method
|
||||
// has returned.
|
||||
type ResponseWriter interface {
|
||||
// Header returns the header map that will be sent by
|
||||
// WriteHeader. The Header map also is the mechanism with which
|
||||
// Handlers can set HTTP trailers.
|
||||
//
|
||||
// Changing the header map after a call to WriteHeader (or
|
||||
// Write) has no effect unless the modified headers are
|
||||
// trailers.
|
||||
//
|
||||
// There are two ways to set Trailers. The preferred way is to
|
||||
// predeclare in the headers which trailers you will later
|
||||
// send by setting the "Trailer" header to the names of the
|
||||
// trailer keys which will come later. In this case, those
|
||||
// keys of the Header map are treated as if they were
|
||||
// trailers. See the example. The second way, for trailer
|
||||
// keys not known to the Handler until after the first Write,
|
||||
// is to prefix the Header map keys with the TrailerPrefix
|
||||
// constant value. See TrailerPrefix.
|
||||
//
|
||||
// To suppress automatic response headers (such as "Date"), set
|
||||
// their value to nil.
|
||||
Header() Header
|
||||
|
||||
// Write writes the data to the connection as part of an HTTP reply.
|
||||
//
|
||||
// If WriteHeader has not yet been called, Write calls
|
||||
// WriteHeader(http.StatusOK) before writing the data. If the Header
|
||||
// does not contain a Content-Type line, Write adds a Content-Type set
|
||||
// to the result of passing the initial 512 bytes of written data to
|
||||
// DetectContentType. Additionally, if the total size of all written
|
||||
// data is under a few KB and there are no Flush calls, the
|
||||
// Content-Length header is added automatically.
|
||||
//
|
||||
// Depending on the HTTP protocol version and the client, calling
|
||||
// Write or WriteHeader may prevent future reads on the
|
||||
// Request.Body. For HTTP/1.x requests, handlers should read any
|
||||
// needed request body data before writing the response. Once the
|
||||
// headers have been flushed (due to either an explicit Flusher.Flush
|
||||
// call or writing enough data to trigger a flush), the request body
|
||||
// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
|
||||
// handlers to continue to read the request body while concurrently
|
||||
// writing the response. However, such behavior may not be supported
|
||||
// by all HTTP/2 clients. Handlers should read before writing if
|
||||
// possible to maximize compatibility.
|
||||
Write([]byte) (int, error)
|
||||
|
||||
// WriteHeader sends an HTTP response header with the provided
|
||||
// status code.
|
||||
//
|
||||
// If WriteHeader is not called explicitly, the first call to Write
|
||||
// will trigger an implicit WriteHeader(http.StatusOK).
|
||||
// Thus explicit calls to WriteHeader are mainly used to
|
||||
// send error codes.
|
||||
//
|
||||
// The provided code must be a valid HTTP 1xx-5xx status code.
|
||||
// Only one header may be written. Go does not currently
|
||||
// support sending user-defined 1xx informational headers,
|
||||
// with the exception of 100-continue response header that the
|
||||
// Server sends automatically when the Request.Body is read.
|
||||
WriteHeader(statusCode int)
|
||||
}
|
||||
|
||||
// TimeFormat is the time format to use when generating times in HTTP
|
||||
// headers. It is like time.RFC1123 but hard-codes GMT as the time
|
||||
// zone. The time being formatted must be in UTC for Format to
|
||||
// generate the correct format.
|
||||
//
|
||||
// For parsing this time format, see ParseTime.
|
||||
const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
|
||||
|
||||
// The HandlerFunc type is an adapter to allow the use of
|
||||
// ordinary functions as HTTP handlers. If f is a function
|
||||
// with the appropriate signature, HandlerFunc(f) is a
|
||||
// Handler that calls f.
|
||||
type HandlerFunc func(ResponseWriter, *Request)
|
||||
|
||||
// ServeHTTP calls f(w, r).
|
||||
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
f(w, r)
|
||||
}
|
||||
|
||||
// Helper handlers
|
||||
|
||||
// Error replies to the request with the specified error message and HTTP code.
|
||||
// It does not otherwise end the request; the caller should ensure no further
|
||||
// writes are done to w.
|
||||
// The error message should be plain text.
|
||||
func Error(w ResponseWriter, error string, code int) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.WriteHeader(code)
|
||||
fmt.Fprintln(w, error)
|
||||
}
|
||||
|
||||
// NotFound replies to the request with an HTTP 404 not found error.
|
||||
func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) }
|
||||
|
||||
// NotFoundHandler returns a simple request handler
|
||||
// that replies to each request with a ``404 page not found'' reply.
|
||||
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
|
||||
|
||||
// StripPrefix returns a handler that serves HTTP requests by removing the
|
||||
// given prefix from the request URL's Path (and RawPath if set) and invoking
|
||||
// the handler h. StripPrefix handles a request for a path that doesn't begin
|
||||
// with prefix by replying with an HTTP 404 not found error. The prefix must
|
||||
// match exactly: if the prefix in the request contains escaped characters
|
||||
// the reply is also an HTTP 404 not found error.
|
||||
func StripPrefix(prefix string, h Handler) Handler {
|
||||
if prefix == "" {
|
||||
return h
|
||||
}
|
||||
return HandlerFunc(func(w ResponseWriter, r *Request) {
|
||||
p := strings.TrimPrefix(r.URL.Path, prefix)
|
||||
rp := strings.TrimPrefix(r.URL.RawPath, prefix)
|
||||
if len(p) < len(r.URL.Path) && (r.URL.RawPath == "" || len(rp) < len(r.URL.RawPath)) {
|
||||
r2 := new(Request)
|
||||
*r2 = *r
|
||||
r2.URL = new(url.URL)
|
||||
*r2.URL = *r.URL
|
||||
r2.URL.Path = p
|
||||
r2.URL.RawPath = rp
|
||||
h.ServeHTTP(w, r2)
|
||||
} else {
|
||||
NotFound(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Redirect replies to the request with a redirect to url,
|
||||
// which may be a path relative to the request path.
|
||||
//
|
||||
// The provided code should be in the 3xx range and is usually
|
||||
// StatusMovedPermanently, StatusFound or StatusSeeOther.
|
||||
//
|
||||
// If the Content-Type header has not been set, Redirect sets it
|
||||
// to "text/html; charset=utf-8" and writes a small HTML body.
|
||||
// Setting the Content-Type header to any value, including nil,
|
||||
// disables that behavior.
|
||||
func Redirect(w ResponseWriter, r *Request, url string, code int) {
|
||||
if u, err := urlpkg.Parse(url); err == nil {
|
||||
// If url was relative, make its path absolute by
|
||||
// combining with request path.
|
||||
// The client would probably do this for us,
|
||||
// but doing it ourselves is more reliable.
|
||||
// See RFC 7231, section 7.1.2
|
||||
if u.Scheme == "" && u.Host == "" {
|
||||
oldpath := r.URL.Path
|
||||
if oldpath == "" { // should not happen, but avoid a crash if it does
|
||||
oldpath = "/"
|
||||
}
|
||||
|
||||
// no leading http://server
|
||||
if url == "" || url[0] != '/' {
|
||||
// make relative path absolute
|
||||
olddir, _ := path.Split(oldpath)
|
||||
url = olddir + url
|
||||
}
|
||||
|
||||
var query string
|
||||
if i := strings.Index(url, "?"); i != -1 {
|
||||
url, query = url[:i], url[i:]
|
||||
}
|
||||
|
||||
// clean up but preserve trailing slash
|
||||
trailing := strings.HasSuffix(url, "/")
|
||||
url = path.Clean(url)
|
||||
if trailing && !strings.HasSuffix(url, "/") {
|
||||
url += "/"
|
||||
}
|
||||
url += query
|
||||
}
|
||||
}
|
||||
|
||||
h := w.Header()
|
||||
|
||||
// RFC 7231 notes that a short HTML body is usually included in
|
||||
// the response because older user agents may not understand 301/307.
|
||||
// Do it only if the request didn't already have a Content-Type header.
|
||||
_, hadCT := h["Content-Type"]
|
||||
|
||||
h.Set("Location", hexEscapeNonASCII(url))
|
||||
if !hadCT && (r.Method == "GET" || r.Method == "HEAD") {
|
||||
h.Set("Content-Type", "text/html; charset=utf-8")
|
||||
}
|
||||
w.WriteHeader(code)
|
||||
|
||||
// Shouldn't send the body for POST or HEAD; that leaves GET.
|
||||
if !hadCT && r.Method == "GET" {
|
||||
body := "<a href=\"" + htmlEscape(url) + "\">" + statusText[code] + "</a>.\n"
|
||||
fmt.Fprintln(w, body)
|
||||
}
|
||||
}
|
||||
|
||||
var htmlReplacer = strings.NewReplacer(
|
||||
"&", "&",
|
||||
"<", "<",
|
||||
">", ">",
|
||||
// """ is shorter than """.
|
||||
`"`, """,
|
||||
// "'" is shorter than "'" and apos was not in HTML until HTML5.
|
||||
"'", "'",
|
||||
)
|
||||
|
||||
func htmlEscape(s string) string {
|
||||
return htmlReplacer.Replace(s)
|
||||
}
|
||||
|
||||
// Redirect to a fixed URL
|
||||
type redirectHandler struct {
|
||||
url string
|
||||
code int
|
||||
}
|
||||
|
||||
func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
Redirect(w, r, rh.url, rh.code)
|
||||
}
|
||||
|
||||
// RedirectHandler returns a request handler that redirects
|
||||
// each request it receives to the given url using the given
|
||||
// status code.
|
||||
//
|
||||
// The provided code should be in the 3xx range and is usually
|
||||
// StatusMovedPermanently, StatusFound or StatusSeeOther.
|
||||
func RedirectHandler(url string, code int) Handler {
|
||||
return &redirectHandler{url, code}
|
||||
}
|
||||
|
||||
// ServeMux is an HTTP request multiplexer.
|
||||
// It matches the URL of each incoming request against a list of registered
|
||||
// patterns and calls the handler for the pattern that
|
||||
// most closely matches the URL.
|
||||
//
|
||||
// Patterns name fixed, rooted paths, like "/favicon.ico",
|
||||
// or rooted subtrees, like "/images/" (note the trailing slash).
|
||||
// Longer patterns take precedence over shorter ones, so that
|
||||
// if there are handlers registered for both "/images/"
|
||||
// and "/images/thumbnails/", the latter handler will be
|
||||
// called for paths beginning "/images/thumbnails/" and the
|
||||
// former will receive requests for any other paths in the
|
||||
// "/images/" subtree.
|
||||
//
|
||||
// Note that since a pattern ending in a slash names a rooted subtree,
|
||||
// the pattern "/" matches all paths not matched by other registered
|
||||
// patterns, not just the URL with Path == "/".
|
||||
//
|
||||
// If a subtree has been registered and a request is received naming the
|
||||
// subtree root without its trailing slash, ServeMux redirects that
|
||||
// request to the subtree root (adding the trailing slash). This behavior can
|
||||
// be overridden with a separate registration for the path without
|
||||
// the trailing slash. For example, registering "/images/" causes ServeMux
|
||||
// to redirect a request for "/images" to "/images/", unless "/images" has
|
||||
// been registered separately.
|
||||
//
|
||||
// Patterns may optionally begin with a host name, restricting matches to
|
||||
// URLs on that host only. Host-specific patterns take precedence over
|
||||
// general patterns, so that a handler might register for the two patterns
|
||||
// "/codesearch" and "codesearch.google.com/" without also taking over
|
||||
// requests for "http://www.google.com/".
|
||||
//
|
||||
// ServeMux also takes care of sanitizing the URL request path and the Host
|
||||
// header, stripping the port number and redirecting any request containing . or
|
||||
// .. elements or repeated slashes to an equivalent, cleaner URL.
|
||||
type ServeMux struct {
|
||||
mu sync.RWMutex
|
||||
m map[string]muxEntry
|
||||
es []muxEntry // slice of entries sorted from longest to shortest.
|
||||
hosts bool // whether any patterns contain hostnames
|
||||
}
|
||||
|
||||
type muxEntry struct {
|
||||
h Handler
|
||||
pattern string
|
||||
}
|
||||
|
||||
// NewServeMux allocates and returns a new ServeMux.
|
||||
func NewServeMux() *ServeMux { return new(ServeMux) }
|
||||
|
||||
// DefaultServeMux is the default ServeMux used by Serve.
|
||||
var DefaultServeMux = &defaultServeMux
|
||||
|
||||
var defaultServeMux ServeMux
|
||||
|
||||
// cleanPath returns the canonical path for p, eliminating . and .. elements.
|
||||
func cleanPath(p string) string {
|
||||
if p == "" {
|
||||
return "/"
|
||||
}
|
||||
if p[0] != '/' {
|
||||
p = "/" + p
|
||||
}
|
||||
np := path.Clean(p)
|
||||
// path.Clean removes trailing slash except for root;
|
||||
// put the trailing slash back if necessary.
|
||||
if p[len(p)-1] == '/' && np != "/" {
|
||||
// Fast path for common case of p being the string we want:
|
||||
if len(p) == len(np)+1 && strings.HasPrefix(p, np) {
|
||||
np = p
|
||||
} else {
|
||||
np += "/"
|
||||
}
|
||||
}
|
||||
return np
|
||||
}
|
||||
|
||||
// stripHostPort returns h without any trailing ":<port>".
|
||||
func stripHostPort(h string) string {
|
||||
// If no port on host, return unchanged
|
||||
if strings.IndexByte(h, ':') == -1 {
|
||||
return h
|
||||
}
|
||||
host, _, err := net.SplitHostPort(h)
|
||||
if err != nil {
|
||||
return h // on error, return unchanged
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
// Find a handler on a handler map given a path string.
|
||||
// Most-specific (longest) pattern wins.
|
||||
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
|
||||
// Check for exact match first.
|
||||
v, ok := mux.m[path]
|
||||
if ok {
|
||||
return v.h, v.pattern
|
||||
}
|
||||
|
||||
// Check for longest valid match. mux.es contains all patterns
|
||||
// that end in / sorted from longest to shortest.
|
||||
for _, e := range mux.es {
|
||||
if strings.HasPrefix(path, e.pattern) {
|
||||
return e.h, e.pattern
|
||||
}
|
||||
}
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// redirectToPathSlash determines if the given path needs appending "/" to it.
|
||||
// This occurs when a handler for path + "/" was already registered, but
|
||||
// not for path itself. If the path needs appending to, it creates a new
|
||||
// URL, setting the path to u.Path + "/" and returning true to indicate so.
|
||||
func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool) {
|
||||
mux.mu.RLock()
|
||||
shouldRedirect := mux.shouldRedirectRLocked(host, path)
|
||||
mux.mu.RUnlock()
|
||||
if !shouldRedirect {
|
||||
return u, false
|
||||
}
|
||||
path = path + "/"
|
||||
u = &url.URL{Path: path, RawQuery: u.RawQuery}
|
||||
return u, true
|
||||
}
|
||||
|
||||
// shouldRedirectRLocked reports whether the given path and host should be redirected to
|
||||
// path+"/". This should happen if a handler is registered for path+"/" but
|
||||
// not path -- see comments at ServeMux.
|
||||
func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool {
|
||||
p := []string{path, host + path}
|
||||
|
||||
for _, c := range p {
|
||||
if _, exist := mux.m[c]; exist {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
n := len(path)
|
||||
if n == 0 {
|
||||
return false
|
||||
}
|
||||
for _, c := range p {
|
||||
if _, exist := mux.m[c+"/"]; exist {
|
||||
return path[n-1] != '/'
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Handler returns the handler to use for the given request,
|
||||
// consulting r.Method, r.Host, and r.URL.Path. It always returns
|
||||
// a non-nil handler. If the path is not in its canonical form, the
|
||||
// handler will be an internally-generated handler that redirects
|
||||
// to the canonical path. If the host contains a port, it is ignored
|
||||
// when matching handlers.
|
||||
//
|
||||
// The path and host are used unchanged for CONNECT requests.
|
||||
//
|
||||
// Handler also returns the registered pattern that matches the
|
||||
// request or, in the case of internally-generated redirects,
|
||||
// the pattern that will match after following the redirect.
|
||||
//
|
||||
// If there is no registered handler that applies to the request,
|
||||
// Handler returns a ``page not found'' handler and an empty pattern.
|
||||
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
|
||||
|
||||
// CONNECT requests are not canonicalized.
|
||||
if r.Method == "CONNECT" {
|
||||
// If r.URL.Path is /tree and its handler is not registered,
|
||||
// the /tree -> /tree/ redirect applies to CONNECT requests
|
||||
// but the path canonicalization does not.
|
||||
if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
|
||||
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
|
||||
}
|
||||
|
||||
return mux.handler(r.Host, r.URL.Path)
|
||||
}
|
||||
|
||||
// All other requests have any port stripped and path cleaned
|
||||
// before passing to mux.handler.
|
||||
host := stripHostPort(r.Host)
|
||||
path := cleanPath(r.URL.Path)
|
||||
|
||||
// If the given path is /tree and its handler is not registered,
|
||||
// redirect for /tree/.
|
||||
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
|
||||
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
|
||||
}
|
||||
|
||||
if path != r.URL.Path {
|
||||
_, pattern = mux.handler(host, path)
|
||||
url := *r.URL
|
||||
url.Path = path
|
||||
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
|
||||
}
|
||||
|
||||
return mux.handler(host, r.URL.Path)
|
||||
}
|
||||
|
||||
// handler is the main implementation of Handler.
|
||||
// The path is known to be in canonical form, except for CONNECT methods.
|
||||
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
|
||||
mux.mu.RLock()
|
||||
defer mux.mu.RUnlock()
|
||||
|
||||
// Host-specific pattern takes precedence over generic ones
|
||||
if mux.hosts {
|
||||
h, pattern = mux.match(host + path)
|
||||
}
|
||||
if h == nil {
|
||||
h, pattern = mux.match(path)
|
||||
}
|
||||
if h == nil {
|
||||
h, pattern = NotFoundHandler(), ""
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ServeHTTP dispatches the request to the handler whose
|
||||
// pattern most closely matches the request URL.
|
||||
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
if r.RequestURI == "*" {
|
||||
if r.ProtoAtLeast(1, 1) {
|
||||
w.Header().Set("Connection", "close")
|
||||
}
|
||||
w.WriteHeader(StatusBadRequest)
|
||||
return
|
||||
}
|
||||
h, _ := mux.Handler(r)
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Handle registers the handler for the given pattern.
|
||||
// If a handler already exists for pattern, Handle panics.
|
||||
func (mux *ServeMux) Handle(pattern string, handler Handler) {
|
||||
mux.mu.Lock()
|
||||
defer mux.mu.Unlock()
|
||||
|
||||
if pattern == "" {
|
||||
panic("http: invalid pattern")
|
||||
}
|
||||
if handler == nil {
|
||||
panic("http: nil handler")
|
||||
}
|
||||
if _, exist := mux.m[pattern]; exist {
|
||||
panic("http: multiple registrations for " + pattern)
|
||||
}
|
||||
|
||||
if mux.m == nil {
|
||||
mux.m = make(map[string]muxEntry)
|
||||
}
|
||||
e := muxEntry{h: handler, pattern: pattern}
|
||||
mux.m[pattern] = e
|
||||
if pattern[len(pattern)-1] == '/' {
|
||||
mux.es = appendSorted(mux.es, e)
|
||||
}
|
||||
|
||||
if pattern[0] != '/' {
|
||||
mux.hosts = true
|
||||
}
|
||||
}
|
||||
|
||||
func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
|
||||
n := len(es)
|
||||
i := sort.Search(n, func(i int) bool {
|
||||
return len(es[i].pattern) < len(e.pattern)
|
||||
})
|
||||
if i == n {
|
||||
return append(es, e)
|
||||
}
|
||||
// we now know that i points at where we want to insert
|
||||
es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.
|
||||
copy(es[i+1:], es[i:]) // Move shorter entries down
|
||||
es[i] = e
|
||||
return es
|
||||
}
|
||||
|
||||
// HandleFunc registers the handler function for the given pattern.
|
||||
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
|
||||
if handler == nil {
|
||||
panic("http: nil handler")
|
||||
}
|
||||
mux.Handle(pattern, HandlerFunc(handler))
|
||||
}
|
||||
|
||||
// Handle registers the handler for the given pattern
|
||||
// in the DefaultServeMux.
|
||||
// The documentation for ServeMux explains how patterns are matched.
|
||||
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
|
||||
|
||||
// HandleFunc registers the handler function for the given pattern
|
||||
// in the DefaultServeMux.
|
||||
// The documentation for ServeMux explains how patterns are matched.
|
||||
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
|
||||
DefaultServeMux.HandleFunc(pattern, handler)
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the TCP network address addr and then calls
|
||||
// Serve with handler to handle requests on incoming connections.
|
||||
// Accepted connections are configured to enable TCP keep-alives.
|
||||
//
|
||||
// The handler is typically nil, in which case the DefaultServeMux is used.
|
||||
//
|
||||
// ListenAndServe always returns a non-nil error.
|
||||
func ListenAndServe(addr string, handler Handler) error {
|
||||
return ActiveDevice.ListenAndServe(addr, handler)
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http
|
||||
|
||||
// HTTP status codes as registered with IANA.
|
||||
// See: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
const (
|
||||
StatusContinue = 100 // RFC 7231, 6.2.1
|
||||
StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
|
||||
StatusProcessing = 102 // RFC 2518, 10.1
|
||||
StatusEarlyHints = 103 // RFC 8297
|
||||
|
||||
StatusOK = 200 // RFC 7231, 6.3.1
|
||||
StatusCreated = 201 // RFC 7231, 6.3.2
|
||||
StatusAccepted = 202 // RFC 7231, 6.3.3
|
||||
StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4
|
||||
StatusNoContent = 204 // RFC 7231, 6.3.5
|
||||
StatusResetContent = 205 // RFC 7231, 6.3.6
|
||||
StatusPartialContent = 206 // RFC 7233, 4.1
|
||||
StatusMultiStatus = 207 // RFC 4918, 11.1
|
||||
StatusAlreadyReported = 208 // RFC 5842, 7.1
|
||||
StatusIMUsed = 226 // RFC 3229, 10.4.1
|
||||
|
||||
StatusMultipleChoices = 300 // RFC 7231, 6.4.1
|
||||
StatusMovedPermanently = 301 // RFC 7231, 6.4.2
|
||||
StatusFound = 302 // RFC 7231, 6.4.3
|
||||
StatusSeeOther = 303 // RFC 7231, 6.4.4
|
||||
StatusNotModified = 304 // RFC 7232, 4.1
|
||||
StatusUseProxy = 305 // RFC 7231, 6.4.5
|
||||
_ = 306 // RFC 7231, 6.4.6 (Unused)
|
||||
StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
|
||||
StatusPermanentRedirect = 308 // RFC 7538, 3
|
||||
|
||||
StatusBadRequest = 400 // RFC 7231, 6.5.1
|
||||
StatusUnauthorized = 401 // RFC 7235, 3.1
|
||||
StatusPaymentRequired = 402 // RFC 7231, 6.5.2
|
||||
StatusForbidden = 403 // RFC 7231, 6.5.3
|
||||
StatusNotFound = 404 // RFC 7231, 6.5.4
|
||||
StatusMethodNotAllowed = 405 // RFC 7231, 6.5.5
|
||||
StatusNotAcceptable = 406 // RFC 7231, 6.5.6
|
||||
StatusProxyAuthRequired = 407 // RFC 7235, 3.2
|
||||
StatusRequestTimeout = 408 // RFC 7231, 6.5.7
|
||||
StatusConflict = 409 // RFC 7231, 6.5.8
|
||||
StatusGone = 410 // RFC 7231, 6.5.9
|
||||
StatusLengthRequired = 411 // RFC 7231, 6.5.10
|
||||
StatusPreconditionFailed = 412 // RFC 7232, 4.2
|
||||
StatusRequestEntityTooLarge = 413 // RFC 7231, 6.5.11
|
||||
StatusRequestURITooLong = 414 // RFC 7231, 6.5.12
|
||||
StatusUnsupportedMediaType = 415 // RFC 7231, 6.5.13
|
||||
StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
|
||||
StatusExpectationFailed = 417 // RFC 7231, 6.5.14
|
||||
StatusTeapot = 418 // RFC 7168, 2.3.3
|
||||
StatusMisdirectedRequest = 421 // RFC 7540, 9.1.2
|
||||
StatusUnprocessableEntity = 422 // RFC 4918, 11.2
|
||||
StatusLocked = 423 // RFC 4918, 11.3
|
||||
StatusFailedDependency = 424 // RFC 4918, 11.4
|
||||
StatusTooEarly = 425 // RFC 8470, 5.2.
|
||||
StatusUpgradeRequired = 426 // RFC 7231, 6.5.15
|
||||
StatusPreconditionRequired = 428 // RFC 6585, 3
|
||||
StatusTooManyRequests = 429 // RFC 6585, 4
|
||||
StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5
|
||||
StatusUnavailableForLegalReasons = 451 // RFC 7725, 3
|
||||
|
||||
StatusInternalServerError = 500 // RFC 7231, 6.6.1
|
||||
StatusNotImplemented = 501 // RFC 7231, 6.6.2
|
||||
StatusBadGateway = 502 // RFC 7231, 6.6.3
|
||||
StatusServiceUnavailable = 503 // RFC 7231, 6.6.4
|
||||
StatusGatewayTimeout = 504 // RFC 7231, 6.6.5
|
||||
StatusHTTPVersionNotSupported = 505 // RFC 7231, 6.6.6
|
||||
StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1
|
||||
StatusInsufficientStorage = 507 // RFC 4918, 11.5
|
||||
StatusLoopDetected = 508 // RFC 5842, 7.2
|
||||
StatusNotExtended = 510 // RFC 2774, 7
|
||||
StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
|
||||
)
|
||||
|
||||
var statusText = map[int]string{
|
||||
StatusContinue: "Continue",
|
||||
StatusSwitchingProtocols: "Switching Protocols",
|
||||
StatusProcessing: "Processing",
|
||||
StatusEarlyHints: "Early Hints",
|
||||
|
||||
StatusOK: "OK",
|
||||
StatusCreated: "Created",
|
||||
StatusAccepted: "Accepted",
|
||||
StatusNonAuthoritativeInfo: "Non-Authoritative Information",
|
||||
StatusNoContent: "No Content",
|
||||
StatusResetContent: "Reset Content",
|
||||
StatusPartialContent: "Partial Content",
|
||||
StatusMultiStatus: "Multi-Status",
|
||||
StatusAlreadyReported: "Already Reported",
|
||||
StatusIMUsed: "IM Used",
|
||||
|
||||
StatusMultipleChoices: "Multiple Choices",
|
||||
StatusMovedPermanently: "Moved Permanently",
|
||||
StatusFound: "Found",
|
||||
StatusSeeOther: "See Other",
|
||||
StatusNotModified: "Not Modified",
|
||||
StatusUseProxy: "Use Proxy",
|
||||
StatusTemporaryRedirect: "Temporary Redirect",
|
||||
StatusPermanentRedirect: "Permanent Redirect",
|
||||
|
||||
StatusBadRequest: "Bad Request",
|
||||
StatusUnauthorized: "Unauthorized",
|
||||
StatusPaymentRequired: "Payment Required",
|
||||
StatusForbidden: "Forbidden",
|
||||
StatusNotFound: "Not Found",
|
||||
StatusMethodNotAllowed: "Method Not Allowed",
|
||||
StatusNotAcceptable: "Not Acceptable",
|
||||
StatusProxyAuthRequired: "Proxy Authentication Required",
|
||||
StatusRequestTimeout: "Request Timeout",
|
||||
StatusConflict: "Conflict",
|
||||
StatusGone: "Gone",
|
||||
StatusLengthRequired: "Length Required",
|
||||
StatusPreconditionFailed: "Precondition Failed",
|
||||
StatusRequestEntityTooLarge: "Request Entity Too Large",
|
||||
StatusRequestURITooLong: "Request URI Too Long",
|
||||
StatusUnsupportedMediaType: "Unsupported Media Type",
|
||||
StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
|
||||
StatusExpectationFailed: "Expectation Failed",
|
||||
StatusTeapot: "I'm a teapot",
|
||||
StatusMisdirectedRequest: "Misdirected Request",
|
||||
StatusUnprocessableEntity: "Unprocessable Entity",
|
||||
StatusLocked: "Locked",
|
||||
StatusFailedDependency: "Failed Dependency",
|
||||
StatusTooEarly: "Too Early",
|
||||
StatusUpgradeRequired: "Upgrade Required",
|
||||
StatusPreconditionRequired: "Precondition Required",
|
||||
StatusTooManyRequests: "Too Many Requests",
|
||||
StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large",
|
||||
StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons",
|
||||
|
||||
StatusInternalServerError: "Internal Server Error",
|
||||
StatusNotImplemented: "Not Implemented",
|
||||
StatusBadGateway: "Bad Gateway",
|
||||
StatusServiceUnavailable: "Service Unavailable",
|
||||
StatusGatewayTimeout: "Gateway Timeout",
|
||||
StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
|
||||
StatusVariantAlsoNegotiates: "Variant Also Negotiates",
|
||||
StatusInsufficientStorage: "Insufficient Storage",
|
||||
StatusLoopDetected: "Loop Detected",
|
||||
StatusNotExtended: "Not Extended",
|
||||
StatusNetworkAuthenticationRequired: "Network Authentication Required",
|
||||
}
|
||||
|
||||
// StatusText returns a text for the HTTP status code. It returns the empty
|
||||
// string if the code is unknown.
|
||||
func StatusText(code int) string {
|
||||
return statusText[code]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// msg is *Request or *Response.
|
||||
func readTransfer(msg *Request, r *bufio.Reader) (err error) {
|
||||
// TODO:
|
||||
return nil
|
||||
}
|
||||
|
||||
// Determine whether to hang up after sending a request and body, or
|
||||
// receiving a response and body
|
||||
// 'header' is the request headers
|
||||
func shouldClose(major, minor int, header Header, removeCloseHeader bool) bool {
|
||||
if major < 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
conv := header["Connection"]
|
||||
hasClose := httpguts.HeaderValuesContainsToken(conv, "close")
|
||||
if major == 1 && minor == 0 {
|
||||
return hasClose || !httpguts.HeaderValuesContainsToken(conv, "keep-alive")
|
||||
}
|
||||
|
||||
if hasClose && removeCloseHeader {
|
||||
header.Del("Connection")
|
||||
}
|
||||
|
||||
return hasClose
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package rtl8720dn
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net/http"
|
||||
)
|
||||
|
||||
func (rtl *RTL8720DN) setupHTTPServer() error {
|
||||
_, err := rtl.Rpc_lwip_close(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = rtl.Rpc_lwip_socket(0x00000002, 0x00000001, 0x00000000)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := []byte{0x00, 0x02, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0xA5, 0x42, 0x00, 0x00, 0xC7, 0x61, 0x01, 0x00}
|
||||
_, err = rtl.Rpc_lwip_bind(0, name, uint32(len(name)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = rtl.Rpc_lwip_listen(0, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = rtl.Rpc_lwip_fcntl(0, 4, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rtl *RTL8720DN) accept() (bool, error) {
|
||||
addr := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE4, 0xD7, 0x00, 0x20, 0x00, 0xD8, 0x00, 0x20}
|
||||
length := uint32(len(addr))
|
||||
ret, err := rtl.Rpc_lwip_accept(0, addr, &length)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return ret == 1, nil
|
||||
}
|
||||
|
||||
func (rtl *RTL8720DN) handleHTTP() error {
|
||||
socket := int32(1)
|
||||
optval := []byte{0x01, 0x00, 0x00, 0x00}
|
||||
_, err := rtl.Rpc_lwip_setsockopt(socket, 0x00000FFF, 8, optval, uint32(len(optval)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = rtl.Rpc_lwip_setsockopt(socket, 6, 1, optval, uint32(len(optval)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
_, err = rtl.Rpc_lwip_recv(socket, &buf, uint32(len(buf)), 8, 0)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if len(buf) > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
_, err = rtl.Rpc_lwip_errno()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
buf2 := make([]byte, 4096)
|
||||
result, err := rtl.Rpc_lwip_recv(socket, &buf2, uint32(len(buf2)), 8, 0)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if result != -1 && result != 0 {
|
||||
return fmt.Errorf("Rpc_lwip_recv error")
|
||||
}
|
||||
|
||||
result, err = rtl.Rpc_lwip_errno()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if result != 11 {
|
||||
return fmt.Errorf("Rpc_lwip_errno error")
|
||||
}
|
||||
|
||||
b := bufio.NewReader(bytes.NewReader(buf))
|
||||
req, err := http.ReadRequest(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rtl.debug {
|
||||
fmt.Printf("%s %s %s\r\n", req.Method, req.RequestURI, req.Proto)
|
||||
}
|
||||
|
||||
pos := bytes.Index(buf, []byte("\r\n\r\n"))
|
||||
if pos > 0 {
|
||||
body := bytes.NewReader(buf[pos+4:])
|
||||
req.Body = io.NopCloser(body)
|
||||
}
|
||||
|
||||
handler, _ := http.DefaultServeMux.Handler(req)
|
||||
rwx := responseWriter{
|
||||
header: http.Header{},
|
||||
statusCode: 200,
|
||||
}
|
||||
rwx.header.Add(`Content-Type`, `text/html; charset=UTF-8`)
|
||||
rwx.header.Add(`Connection`, `close`)
|
||||
handler.ServeHTTP(&rwx, req)
|
||||
rwx.header.Add(`Content-Length`, fmt.Sprintf("%d", len(rwx.Buf)))
|
||||
|
||||
optval = []byte{0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xA5, 0xA5, 0xA5}
|
||||
_, err = rtl.Rpc_lwip_setsockopt(socket, 0x00000FFF, 0x1006, optval, uint32(len(optval)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
optval = []byte{0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xA5, 0xA5, 0xA5}
|
||||
_, err = rtl.Rpc_lwip_setsockopt(socket, 0x00000FFF, 0x1005, optval, uint32(len(optval)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
maxfdp1 := int32(2)
|
||||
writeset := []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
timeout := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x42, 0x0F, 0x00, 0x0A, 0x00, 0x00, 0x00}
|
||||
_, err = rtl.Rpc_lwip_select(maxfdp1, []byte{}, writeset, []byte{}, timeout)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := rwx.Buf
|
||||
hb := bytes.Buffer{}
|
||||
err = rwx.header.Write(&hb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := []byte(fmt.Sprintf("HTTP/1.1 %d OK\n", rwx.statusCode))
|
||||
data = append(data, hb.Bytes()...)
|
||||
data = append(data, byte('\n'))
|
||||
|
||||
_, err = rtl.Rpc_lwip_send(socket, data, 8)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(msg) > 0 {
|
||||
timeout = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x42, 0x0F, 0x00, 0x54, 0x00, 0x00, 0x00}
|
||||
_, err = rtl.Rpc_lwip_select(maxfdp1, []byte{}, writeset, []byte{}, timeout)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = rtl.Rpc_lwip_send(socket, msg, 8)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
buf := make([]byte, 4096)
|
||||
_, err = rtl.Rpc_lwip_recv(socket, &buf, uint32(len(buf)), 8, 0)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = rtl.Rpc_lwip_errno()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
_, err = rtl.Rpc_lwip_close(socket)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rtl *RTL8720DN) ListenAndServe(addr string, handler http.Handler) error {
|
||||
err := rtl.setupHTTPServer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
connected, err := rtl.accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if connected {
|
||||
err := rtl.handleHTTP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
type responseWriter struct {
|
||||
Buf []byte
|
||||
header http.Header
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (r *responseWriter) Header() http.Header {
|
||||
return r.header
|
||||
}
|
||||
|
||||
func (r *responseWriter) Write(b []byte) (int, error) {
|
||||
r.Buf = append(r.Buf, b...)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (r *responseWriter) WriteHeader(statusCode int) {
|
||||
r.statusCode = statusCode
|
||||
}
|
||||
@@ -12,8 +12,8 @@ func (r *RTL8720DN) GetDNS(domain string) (string, error) {
|
||||
fmt.Printf("GetDNS(%q)\r\n", domain)
|
||||
}
|
||||
|
||||
ipaddr := [4]byte{}
|
||||
_, err := r.Rpc_netconn_gethostbyname(domain, ipaddr[:])
|
||||
ipaddr := make([]byte, 4)
|
||||
_, err := r.Rpc_netconn_gethostbyname(domain, &ipaddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -31,8 +31,8 @@ func (r *RTL8720DN) ConnectTCPSocket(addr, port string) error {
|
||||
fmt.Printf("ConnectTCPSocket(%q, %q)\r\n", addr, port)
|
||||
}
|
||||
|
||||
ipaddr := [4]byte{}
|
||||
_, err := r.Rpc_netconn_gethostbyname(addr, ipaddr[:])
|
||||
ipaddr := make([]byte, 4)
|
||||
_, err := r.Rpc_netconn_gethostbyname(addr, &ipaddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -81,8 +81,9 @@ func (r *RTL8720DN) ConnectTCPSocket(addr, port string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
optlen := uint32(4)
|
||||
_, err = r.Rpc_lwip_getsockopt(socket, 0x00000FFF, 0x00001007, []byte{0xA5, 0xA5, 0xA5, 0xA5}, nil, optlen)
|
||||
optval := make([]byte, 4)
|
||||
optlen := uint32(len(optval))
|
||||
_, err = r.Rpc_lwip_getsockopt(socket, 0x00000FFF, 0x00001007, []byte{0xA5, 0xA5, 0xA5, 0xA5}, &optval, &optlen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -225,7 +226,7 @@ func (r *RTL8720DN) ReadSocket(b []byte) (n int, err error) {
|
||||
|
||||
switch r.connectionType {
|
||||
case ConnectionTypeTCP:
|
||||
nn, err := r.Rpc_lwip_recv(r.socket, b, uint32(len(b)), 0x00000008, 0x00002800)
|
||||
nn, err := r.Rpc_lwip_recv(r.socket, &b, uint32(len(b)), 0x00000008, 0x00002800)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -245,7 +246,7 @@ func (r *RTL8720DN) ReadSocket(b []byte) (n int, err error) {
|
||||
}
|
||||
n = int(nn)
|
||||
case ConnectionTypeTLS:
|
||||
nn, err := r.Rpc_wifi_get_ssl_receive(r.client, b, int32(len(b)))
|
||||
nn, err := r.Rpc_wifi_get_ssl_receive(r.client, &b, int32(len(b)))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
+235
-162
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -59,7 +59,7 @@ func (r *RTL8720DN) ConnectToAP(ssid string, password string) error {
|
||||
|
||||
func (r *RTL8720DN) GetIP() (ip, subnet, gateway IPAddress, err error) {
|
||||
ip_info := make([]byte, 12)
|
||||
_, err = r.Rpc_tcpip_adapter_get_ip_info(0, ip_info)
|
||||
_, err = r.Rpc_tcpip_adapter_get_ip_info(0, &ip_info)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user