commit f0f1e83c60b746aa15612eefaf5fab313b850479 Author: Fedorov Vladimir Date: Sat Feb 3 23:26:09 2024 +0700 create mini http client diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3a39dec --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.3crabs.ru/VLADIMIR/net + +go 1.20 diff --git a/http/client.go b/http/client.go new file mode 100644 index 0000000..f60e4e0 --- /dev/null +++ b/http/client.go @@ -0,0 +1,87 @@ +package http + +import ( + "bufio" + "errors" + "fmt" + "io" + "net" + "strconv" + "strings" +) + +func Do(method string, url string) (*Response, error) { + path := "/" + arr := strings.Split(url, "/") + if len(arr) == 2 { + path = arr[1] + } + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + + conn, err := net.Dial("tcp", arr[0]) + if err != nil { + return nil, errors.New("connect " + arr[0]) + } + defer conn.Close() + + WriteRequest(conn, method, path, "HTTP/1.0") + return ReadResponse(conn) +} + +func WriteRequest(w io.Writer, method string, path string, httpVersion string) { + fmt.Fprintf(w, "%s %s %s\r\n\r\n", method, path, httpVersion) +} + +func ReadResponse(r io.Reader) (*Response, error) { + response := &Response{} + + b := bufio.NewReader(r) + + status, err := b.ReadString('\n') + if err != nil { + return response, errors.New("read status") + } + arr := strings.Split(status, " ") + response.StatusCode, err = strconv.Atoi(arr[1]) + if err != nil { + return response, errors.New("read status code") + } + + headerStr := "" + for { + headerStr, err = b.ReadString('\n') + if err != nil { + return response, errors.New("read header") + } + if headerStr == "\r\n" { + break + } + arr := strings.Split(headerStr, ": ") + value := strings.TrimSpace(arr[1]) + response.Headers = append(response.Headers, &Header{Name: arr[0], Value: value}) + } + + contentTypeHeader, err := response.GetHeader("Content-Length") + if err != nil { + return response, errors.New("get Content-Length") + } + length, err := strconv.Atoi(contentTypeHeader.Value) + if err != nil { + return response, errors.New("read Content-Length") + } + + body := make([]byte, 0, length) + + for i := 0; i < length; i++ { + r, err := b.ReadByte() + if err != nil { + return response, errors.New("read body") + } + body = append(body, r) + } + response.Body = body + + return response, nil +} diff --git a/http/header.go b/http/header.go new file mode 100644 index 0000000..e4928ba --- /dev/null +++ b/http/header.go @@ -0,0 +1,6 @@ +package http + +type Header struct { + Name string + Value string +} diff --git a/http/response.go b/http/response.go new file mode 100644 index 0000000..249ba33 --- /dev/null +++ b/http/response.go @@ -0,0 +1,24 @@ +package http + +import ( + "errors" +) + +var ( + ErrHeaderNotFound = errors.New("header not found") +) + +type Response struct { + StatusCode int + Headers []*Header + Body []byte +} + +func (r *Response) GetHeader(name string) (*Header, error) { + for _, h := range r.Headers { + if h.Name == name { + return h, nil + } + } + return nil, ErrHeaderNotFound +} diff --git a/test_client/main.go b/test_client/main.go new file mode 100644 index 0000000..608fad3 --- /dev/null +++ b/test_client/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + + "git.3crabs.ru/VLADIMIR/net/http" +) + +func main() { + r, err := http.Do("GET", "127.0.0.1:8081") + if err != nil { + panic(err) + } + fmt.Println(r.StatusCode, string(r.Body)) +} diff --git a/test_server/main.go b/test_server/main.go new file mode 100644 index 0000000..3faa1d0 --- /dev/null +++ b/test_server/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "net/http" +) + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello World!") + w.WriteHeader(200) + }) + http.ListenAndServe(":8081", nil) +}