Skip to content

Commit

Permalink
WIP(x/http/client/get): Introducing textproto for header & implementi…
Browse files Browse the repository at this point in the history
…ng custom header
  • Loading branch information
spongehah committed Aug 12, 2024
1 parent b9f4944 commit 2c9394c
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 25 deletions.
45 changes: 45 additions & 0 deletions x/http/_demo/headers/headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

import (
"fmt"
"io"

"github.com/goplus/llgo/x/http"
)

func main() {
client := &http.Client{}
req, err := http.NewRequest("GET", "https://jsonplaceholder.typicode.com/comments?postId=1", nil)
if err != nil {
println(err.Error())
return
}

//req.Header.Set("accept", "*/*")
//req.Header.Set("accept-encoding", "identity")
//req.Header.Set("cache-control", "no-cache")
//req.Header.Set("pragma", "no-cache")
//req.Header.Set("priority", "u=0, i")
//req.Header.Set("referer", "https://jsonplaceholder.typicode.com/")
//req.Header.Set("sec-ch-ua", "\"Not)A;Brand\";v=\"99\", \"Google Chrome\";v=\"127\", \"Chromium\";v=\"127\"")
//req.Header.Set("sec-ch-ua-mobile", "?0")
//req.Header.Set("sec-ch-ua-platform", "\"macOS\"")
//req.Header.Set("sec-fetch-dest", "document")
//req.Header.Set("sec-fetch-mode", "navigate")
//req.Header.Set("sec-fetch-site", "same-origin")
//req.Header.Set("sec-fetch-user", "?1")
//req.Header.Set("upgrade-insecure-requests", "1")
//req.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")

resp, err := client.Do(req)
if err != nil {
println(err.Error())
return
}
body, err := io.ReadAll(resp.Body)
if err != nil {
println(err.Error())
return
}
fmt.Println(string(body))
}
5 changes: 5 additions & 0 deletions x/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func (c *Client) Do(req *Request) (*Response, error) {
}

func (c *Client) do(req *Request) (*Response, error) {
// Add user-defined request headers to hyper.Request
err := req.setHeaders()
if err != nil {
return nil, err
}
return c.send(req, c.Timeout)
}

Expand Down
67 changes: 65 additions & 2 deletions x/http/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,74 @@ import (
"fmt"

"github.com/goplus/llgo/c"
"github.com/goplus/llgo/x/textproto"

Check failure on line 7 in x/http/header.go

View workflow job for this annotation

GitHub Actions / build

no required module provides package github.com/goplus/llgo/x/textproto; to add it:
"github.com/goplus/llgoexamples/rust/hyper"
)

// 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. Get assumes that all
// keys are stored in canonical form. 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)
}

// AppendToResponseHeader (HeadersForEachCallback) prints each header to the console
func AppendToResponseHeader(userdata c.Pointer, name *uint8, nameLen uintptr, value *uint8, valueLen uintptr) c.Int {
resp := (*Response)(userdata)
Expand All @@ -18,8 +81,8 @@ func AppendToResponseHeader(userdata c.Pointer, name *uint8, nameLen uintptr, va
if resp.Header == nil {
resp.Header = make(map[string][]string)
}
resp.Header[nameStr] = append(resp.Header[nameStr], valueStr)
//c.Printf(c.Str("%.*s: %.*s\n"), int(nameLen), name, int(valueLen), value)
resp.Header.Add(nameStr, valueStr)
//resp.Header[nameStr] = append(resp.Header[nameStr], valueStr)
return hyper.IterContinue
}

Expand Down
33 changes: 29 additions & 4 deletions x/http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@ func NewRequest(method, urlStr string, body io.Reader) (*Request, error) {
if err != nil {
return nil, err
}
return &Request{
request := &Request{
Method: method,
URL: parseURL,
Req: req,
Host: parseURL.Hostname(),
Header: make(Header),
timeout: 0,
}, nil
}
request.Header.Set("Host", request.Host)
return request, nil
}

func newHyperRequest(method string, URL *url.URL) (*hyper.Request, error) {
Expand All @@ -49,11 +52,33 @@ func newHyperRequest(method string, URL *url.URL) (*hyper.Request, error) {
if req.SetURI((*uint8)(&[]byte(uri)[0]), c.Strlen(c.AllocaCStr(uri))) != hyper.OK {
return nil, fmt.Errorf("error setting uri %s\n", uri)
}

// Set the request headers
reqHeaders := req.Headers()
if reqHeaders.Set((*uint8)(&[]byte("Host")[0]), c.Strlen(c.Str("Host")), (*uint8)(&[]byte(host)[0]), c.Strlen(c.AllocaCStr(host))) != hyper.OK {
return nil, fmt.Errorf("error setting headers\n")
return nil, fmt.Errorf("error setting header: Host: %s\n", host)
}

return req, nil
}

// setHeaders sets the headers of the request
func (req *Request) setHeaders() error {
headers := req.Req.Headers()
for key, values := range req.Header {
valueLen := len(values)
if valueLen > 1 {
for _, value := range values {
if headers.Add((*uint8)(&[]byte(key)[0]), c.Strlen(c.AllocaCStr(key)), (*uint8)(&[]byte(value)[0]), c.Strlen(c.AllocaCStr(value))) != hyper.OK {
return fmt.Errorf("error adding header %s: %s\n", key, value)
}
}
} else if valueLen == 1 {
if headers.Set((*uint8)(&[]byte(key)[0]), c.Strlen(c.AllocaCStr(key)), (*uint8)(&[]byte(values[0])[0]), c.Strlen(c.AllocaCStr(values[0]))) != hyper.OK {
return fmt.Errorf("error setting header %s: %s\n", key, values[0])
}
} else {
return fmt.Errorf("error setting header %s: empty value\n", key)
}
}
return nil
}
14 changes: 0 additions & 14 deletions x/http/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,3 @@ type Response struct {
Body io.ReadCloser
ContentLength int64
}

// AppendToResponseBody (BodyForEachCallback) appends the body to the response
//func AppendToResponseBody(userdata c.Pointer, chunk *hyper.Buf) c.Int {
// resp := (*Response)(userdata)
// len := chunk.Len()
// buf := unsafe.Slice((*byte)(chunk.Bytes()), len)
// _, err := resp.respBodyWriter.Write(buf)
// resp.ContentLength += int64(len)
// if err != nil {
// fmt.Printf("Failed to write response body: %v\n", err)
// return hyper.IterBreak
// }
// return hyper.IterContinue
//}
25 changes: 20 additions & 5 deletions x/http/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ func (pc *persistConn) roundTrip(req *Request) (*Response, error) {
rc := <-pc.reqch // blocking
// Free the resources
FreeResources(nil, nil, nil, nil, pc, rc)
return nil, fmt.Errorf("request timeout\n")
}
select {
case re := <-resc:
Expand Down Expand Up @@ -297,12 +298,26 @@ func (pc *persistConn) readWriteLoop(loop *libuv.Loop) {

response.Body, bodyWriter = io.Pipe()

// TODO(spongehah) Replace header operations with using the textproto package
lengthSlice := response.Header["content-length"]
if lengthSlice == nil {
response.ContentLength = 0
//// TODO(spongehah) Replace header operations with using the textproto package
//lengthSlice := response.Header["content-length"]
//if lengthSlice == nil {
// response.ContentLength = -1
//} else {
// contentLength := response.Header["content-length"][0]
// length, err := strconv.Atoi(contentLength)
// if err != nil {
// rc.ch <- responseAndError{err: fmt.Errorf("failed to parse content-length")}
// // Free the resources
// FreeResources(task, respBody, bodyWriter, exec, pc, rc)
// return
// }
// response.ContentLength = int64(length)
//}

contentLength := response.Header.Get("content-length")
if contentLength == "" {
response.ContentLength = -1
} else {
contentLength := response.Header["content-length"][0]
length, err := strconv.Atoi(contentLength)
if err != nil {
rc.ch <- responseAndError{err: fmt.Errorf("failed to parse content-length")}
Expand Down

0 comments on commit 2c9394c

Please sign in to comment.