package http import ( "bufio" "crypto/tls" "errors" "fmt" "io" "strconv" "strings" ) var ( CRLF = []byte("\r\n") SP = []byte(" ") ) func Do(method string, url string, headers []*Header) (*Response, error) { url = strings.TrimPrefix(url, "http://") url = strings.TrimPrefix(url, "https://") path := "/" arr := strings.Split(url, "/") if len(arr) == 2 { path = arr[1] } if !strings.HasPrefix(path, "/") { path = "/" + path } connectPath := arr[0] // conn, err := net.Dial("tcp", connectPath) conn, err := tls.Dial("tcp", connectPath, nil) if err != nil { return nil, errors.New("connect " + connectPath) } defer conn.Close() WriteRequest(conn, method, arr[0], path, "HTTP/1.0", headers) return ReadResponse(conn) } func WriteRequest( w io.Writer, method string, host string, path string, httpVersion string, headers []*Header, ) { io.WriteString(w, method) w.Write(SP) io.WriteString(w, path) w.Write(SP) io.WriteString(w, httpVersion) w.Write(CRLF) for _, header := range headers { WriteHeader(w, header.Name, header.Value) } // headers end w.Write(CRLF) // todo: body } func WriteHeader(w io.Writer, name string, value string) { fmt.Fprintf(w, "%s:%s", name, value) w.Write(CRLF) } func ReadResponse(r io.Reader) (*Response, error) { b := bufio.NewReader(r) if _, err := b.ReadString(' '); err != nil { return nil, errors.New("read httpVersion") } statusCodeStr, err := b.ReadString(' ') if err != nil { return nil, errors.New("read statusCode") } if _, err := b.ReadString('\r'); err != nil { return nil, errors.New("read statusName") } if _, err := b.ReadString('\n'); err != nil { return nil, errors.New("read LF") } statusCode, err := strconv.Atoi(statusCodeStr[:len(statusCodeStr)-1]) if err != nil { return nil, errors.New("read statusCode") } headerStr := "" contentLengthStr := "" headers := []*Header{} for { headerStr, err = b.ReadString('\n') if err != nil { return nil, errors.New("read header") } if headerStr == "\r\n" { break } arr := strings.Split(headerStr, ": ") value := strings.TrimSpace(arr[1]) header := &Header{Name: arr[0], Value: value} if header.Name == "Content-Length" { contentLengthStr = header.Value } headers = append(headers, header) } length, err := strconv.Atoi(contentLengthStr) if err != nil { return nil, errors.New("read Content-Length") } body := make([]byte, 0, length) for i := 0; i < length; i++ { r, err := b.ReadByte() if err != nil { return nil, errors.New("read body") } body = append(body, r) } return &Response{ StatusCode: statusCode, Headers: headers, Body: body, }, nil }