mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 18:48:41 +00:00
Compare commits
7 Commits
lora-usa
...
http-chunked
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fc9dd0670 | |||
| 479beeeece | |||
| 2526660a96 | |||
| 4213d6f582 | |||
| 81cf507e4e | |||
| 86cc0e31f1 | |||
| e3e95cacfe |
@@ -2,6 +2,8 @@ package http
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -211,3 +213,41 @@ func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response,
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
// PostForm issues a POST to the specified URL, with data's keys and
|
||||
// values URL-encoded as the request body.
|
||||
//
|
||||
// The Content-Type header is set to application/x-www-form-urlencoded.
|
||||
// To set other headers, use NewRequest and DefaultClient.Do.
|
||||
//
|
||||
// When err is nil, resp always contains a non-nil resp.Body.
|
||||
// Caller should close resp.Body when done reading from it.
|
||||
//
|
||||
// PostForm is a wrapper around DefaultClient.PostForm.
|
||||
//
|
||||
// See the Client.Do method documentation for details on how redirects
|
||||
// are handled.
|
||||
//
|
||||
// To make a request with a specified context.Context, use NewRequestWithContext
|
||||
// and DefaultClient.Do.
|
||||
func PostForm(url string, data url.Values) (resp *Response, err error) {
|
||||
return DefaultClient.PostForm(url, data)
|
||||
}
|
||||
|
||||
// PostForm issues a POST to the specified URL,
|
||||
// with data's keys and values URL-encoded as the request body.
|
||||
//
|
||||
// The Content-Type header is set to application/x-www-form-urlencoded.
|
||||
// To set other headers, use NewRequest and Client.Do.
|
||||
//
|
||||
// When err is nil, resp always contains a non-nil resp.Body.
|
||||
// Caller should close resp.Body when done reading from it.
|
||||
//
|
||||
// See the Client.Do method documentation for details on how redirects
|
||||
// are handled.
|
||||
//
|
||||
// To make a request with a specified context.Context, use NewRequestWithContext
|
||||
// and Client.Do.
|
||||
func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) {
|
||||
return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
||||
}
|
||||
|
||||
+140
-123
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -25,17 +26,43 @@ func (c *Client) Do(req *Request) (*Response, error) {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
transport := c.Transport
|
||||
if transport == nil {
|
||||
transport = DefaultTransport
|
||||
}
|
||||
res, err := transport.RoundTrip(req)
|
||||
|
||||
if c.Jar != nil {
|
||||
if rc := res.Cookies(); len(rc) > 0 {
|
||||
c.Jar.SetCookies(req.URL, rc)
|
||||
}
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
type Transport struct {
|
||||
}
|
||||
|
||||
var DefaultTransport RoundTripper
|
||||
|
||||
func init() {
|
||||
DefaultTransport = &Transport{}
|
||||
}
|
||||
|
||||
func (t *Transport) RoundTrip(req *Request) (*Response, error) {
|
||||
switch req.URL.Scheme {
|
||||
case "http":
|
||||
return c.doHTTP(req)
|
||||
return t.doHTTP(req)
|
||||
case "https":
|
||||
return c.doHTTPS(req)
|
||||
return t.doHTTPS(req)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid schemer : %s", req.URL.Scheme)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) doHTTP(req *Request) (*Response, error) {
|
||||
func (t *Transport) doHTTP(req *Request) (*Response, error) {
|
||||
// make TCP connection
|
||||
ip := net.ParseIP(req.URL.Hostname())
|
||||
port := 80
|
||||
@@ -106,10 +133,10 @@ func (c *Client) doHTTP(req *Request) (*Response, error) {
|
||||
|
||||
}
|
||||
|
||||
return c.doResp(conn, req)
|
||||
return t.doResp(conn, req)
|
||||
}
|
||||
|
||||
func (c *Client) doHTTPS(req *Request) (*Response, error) {
|
||||
func (t *Transport) doHTTPS(req *Request) (*Response, error) {
|
||||
conn, err := tls.Dial("tcp", req.URL.Host, nil)
|
||||
retry := 0
|
||||
for ; err != nil; conn, err = tls.Dial("tcp", req.URL.Host, nil) {
|
||||
@@ -167,141 +194,131 @@ func (c *Client) doHTTPS(req *Request) (*Response, error) {
|
||||
|
||||
}
|
||||
|
||||
return c.doResp(conn, req)
|
||||
return t.doResp(conn, req)
|
||||
}
|
||||
|
||||
func (c *Client) doResp(conn net.Conn, req *Request) (*Response, error) {
|
||||
func (t *Transport) doResp(conn net.Conn, req *Request) (*Response, error) {
|
||||
resp := &Response{
|
||||
Header: map[string][]string{},
|
||||
}
|
||||
|
||||
// Header
|
||||
var scanner *bufio.Scanner
|
||||
cont := true
|
||||
ofs := 0
|
||||
remain := int64(0)
|
||||
for cont {
|
||||
for n, err := conn.Read(buf[ofs:]); n > 0; n, err = conn.Read(buf[ofs:]) {
|
||||
if err != nil {
|
||||
println("Read error: " + err.Error())
|
||||
} else {
|
||||
// Take care of the case where "\r\n\r\n" is on the boundary of a buffer
|
||||
start := ofs
|
||||
if start > 3 {
|
||||
start -= 3
|
||||
}
|
||||
idx := bytes.Index(buf[start:ofs+n], []byte("\r\n\r\n"))
|
||||
if idx == -1 {
|
||||
ofs += n
|
||||
continue
|
||||
}
|
||||
idx += start + 4
|
||||
br := bufio.NewReader(conn)
|
||||
tp := textproto.NewReader(br)
|
||||
|
||||
scanner = bufio.NewScanner(bytes.NewReader(buf[0 : ofs+n]))
|
||||
if resp.Status == "" && scanner.Scan() {
|
||||
status := strings.SplitN(scanner.Text(), " ", 2)
|
||||
if len(status) != 2 {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("invalid status : %q", scanner.Text())
|
||||
}
|
||||
resp.Proto = status[0]
|
||||
fmt.Sscanf(status[0], "HTTP/%d.%d", &resp.ProtoMajor, &resp.ProtoMinor)
|
||||
|
||||
resp.Status = status[1]
|
||||
fmt.Sscanf(status[1], "%d", &resp.StatusCode)
|
||||
}
|
||||
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
if text == "" {
|
||||
// end of header
|
||||
if idx < n+ofs {
|
||||
ofs = ofs + n - idx
|
||||
for i := 0; i < ofs; i++ {
|
||||
buf[i] = buf[i+idx]
|
||||
}
|
||||
} else {
|
||||
ofs = 0
|
||||
}
|
||||
break
|
||||
} else {
|
||||
header := strings.SplitN(text, ": ", 2)
|
||||
if len(header) != 2 {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("invalid header : %q", text)
|
||||
}
|
||||
if resp.Header.Get(header[0]) == "" {
|
||||
resp.Header.Set(header[0], header[1])
|
||||
} else {
|
||||
resp.Header.Add(header[0], header[1])
|
||||
}
|
||||
|
||||
if strings.ToLower(header[0]) == "content-length" {
|
||||
resp.ContentLength, err = strconv.ParseInt(header[1], 10, 64)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
remain = resp.ContentLength
|
||||
}
|
||||
}
|
||||
}
|
||||
cont = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Body
|
||||
remain -= int64(ofs)
|
||||
if remain <= 0 {
|
||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:ofs]))
|
||||
if c.Jar != nil {
|
||||
if rc := resp.Cookies(); len(rc) > 0 {
|
||||
c.Jar.SetCookies(req.URL, rc)
|
||||
}
|
||||
}
|
||||
return resp, conn.Close()
|
||||
}
|
||||
|
||||
cont = true
|
||||
lastRequestTime := time.Now()
|
||||
for cont {
|
||||
for {
|
||||
end := ofs + 0x400
|
||||
if len(buf) < end {
|
||||
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", end)
|
||||
}
|
||||
n, err := conn.Read(buf[ofs : ofs+0x400])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
for {
|
||||
line, err := tp.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.ErrNoProgress {
|
||||
// default: no timeout
|
||||
continue
|
||||
}
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status := strings.SplitN(line, " ", 2)
|
||||
if len(status) != 2 {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("invalid status : %q", line)
|
||||
}
|
||||
resp.Proto = status[0]
|
||||
fmt.Sscanf(status[0], "HTTP/%d.%d", &resp.ProtoMajor, &resp.ProtoMinor)
|
||||
|
||||
resp.Status = status[1]
|
||||
fmt.Sscanf(status[1], "%d", &resp.StatusCode)
|
||||
break
|
||||
}
|
||||
|
||||
m, err := tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range m {
|
||||
//fmt.Printf("%s: %s\n", k, v)
|
||||
|
||||
if strings.ToLower(k) == "content-length" {
|
||||
resp.ContentLength, err = strconv.ParseInt(v[0], 10, 64)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
ofs += n
|
||||
remain -= int64(n)
|
||||
if remain <= 0 {
|
||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:ofs]))
|
||||
cont = false
|
||||
break
|
||||
}
|
||||
if time.Now().Sub(lastRequestTime).Milliseconds() >= 1000 {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("time out")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if resp.Header.Get(k) == "" {
|
||||
resp.Header.Set(k, v[0])
|
||||
v = v[1:]
|
||||
}
|
||||
for _, vv := range v {
|
||||
resp.Header.Add(k, vv)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if c.Jar != nil {
|
||||
if rc := resp.Cookies(); len(rc) > 0 {
|
||||
c.Jar.SetCookies(req.URL, rc)
|
||||
if resp.Header.Get("Transfer-Encoding") == "chunked" {
|
||||
// chunked
|
||||
cur := 0
|
||||
end := 0
|
||||
for {
|
||||
length := 0
|
||||
if len(buf) < cur+6 {
|
||||
// This is not a very accurate check, but in many cases it should be fine.
|
||||
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", cur+6)
|
||||
}
|
||||
for i := 0; ; i++ {
|
||||
buf[cur+i], err = br.ReadByte()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
length = i + 1
|
||||
if i > 1 && buf[cur+i-1] == '\r' && buf[cur+i] == '\n' {
|
||||
break
|
||||
}
|
||||
}
|
||||
//fmt.Printf("cur:%d length:%d\n", cur, length)
|
||||
|
||||
size, err := strconv.ParseInt(string(buf[cur:cur+length-2]), 16, 64)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
//cur += length
|
||||
//fmt.Printf("cur:%d length:%d size:%d\n", cur, length, size)
|
||||
|
||||
end = cur + int(size) + 2 // size + 2 (\r\n)
|
||||
if len(buf) < end {
|
||||
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", end)
|
||||
}
|
||||
for i := 0; i < int(size)+2; i++ {
|
||||
buf[cur+i], err = br.ReadByte()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
cur += int(size)
|
||||
|
||||
if size == 0 {
|
||||
end = end - 2
|
||||
break
|
||||
}
|
||||
}
|
||||
//fmt.Printf("%q\n", buf[:end])
|
||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:end]))
|
||||
} else {
|
||||
end := int(resp.ContentLength)
|
||||
if len(buf) < end {
|
||||
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", end)
|
||||
}
|
||||
for i := 0; i < end; i++ {
|
||||
buf[i], err = br.ReadByte()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:end]))
|
||||
}
|
||||
|
||||
return resp, conn.Close()
|
||||
|
||||
@@ -71,10 +71,13 @@ func (d *Driver) ConnectTCPSocket(addr, port string) error {
|
||||
name[6] = byte(ipaddr[2])
|
||||
name[7] = byte(ipaddr[3])
|
||||
|
||||
_, err = d.Rpc_lwip_connect(socket, name, uint32(len(name)))
|
||||
result, err := d.Rpc_lwip_connect(socket, name, uint32(len(name)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result == -1 {
|
||||
return fmt.Errorf("failed to connect to %d.%d.%d.%d port %s", addr[0], addr[1], addr[2], addr[3], port)
|
||||
}
|
||||
|
||||
readset := []byte{}
|
||||
writeset := []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
@@ -341,8 +344,11 @@ func (d *Driver) ReadSocket(b []byte) (n int, err error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if nn < 0 {
|
||||
return 0, fmt.Errorf("error %d", n)
|
||||
if nn == -76 {
|
||||
// no data
|
||||
return 0, nil
|
||||
} else if nn < 0 {
|
||||
return 0, fmt.Errorf("error %d", nn)
|
||||
} else if nn == 0 || nn == -30848 {
|
||||
return 0, d.DisconnectSocket()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user