package request import ( "bytes" "fmt" "io" "strconv" "strings" "httpfromtcp/internal/headers" ) type Request struct { RequestLine RequestLine Headers *headers.Headers Body []byte state parserState } type RequestLine struct { HttpVersion string RequestTarget string Method string } var ERROR_BAD_START_LINE = fmt.Errorf("bad start line") var SEPARATOR = []byte("\r\n") type parserState string const ( StateInit parserState = "init" StateDone parserState = "done" StateHeaders parserState = "headers" StateBody parserState = "body" ) func getInt(headers *headers.Headers, name string, defaultValue int) int { valueStr, exists := headers.Get(name) if !exists { return defaultValue } value, err := strconv.Atoi(strings.TrimSpace(valueStr)) if err != nil { return defaultValue } return value } func newRequest() *Request { return &Request{state: StateInit, Headers: headers.NewHeaders()} } func (r *RequestLine) ValidHTTP() bool { return r.HttpVersion == "1.1" } func parseRequestLine(b []byte) (*RequestLine, int, error) { idx := bytes.Index(b, SEPARATOR) if idx == -1 { return nil, 0, nil } startLine := b[:idx] read := idx + len(SEPARATOR) parts := bytes.Split(startLine, []byte(" ")) if len(parts) != 3 { return nil, 0, ERROR_BAD_START_LINE } httpParts := bytes.Split(parts[2], []byte("/")) if len(httpParts) != 2 || string(httpParts[0]) != "HTTP" || string(httpParts[1]) != "1.1" { return nil, 0, ERROR_BAD_START_LINE } rl := &RequestLine{ Method: string(parts[0]), RequestTarget: string(parts[1]), HttpVersion: string(httpParts[1]), } if !rl.ValidHTTP() { return nil, 0, ERROR_BAD_START_LINE } return rl, read, nil } func (r *Request) parse(data []byte) (int, error) { read := 0 outer: for { currentData := data[read:] if len(currentData) == 0 { break outer } switch r.state { case StateInit: rl, n, err := parseRequestLine(currentData) if err != nil { fmt.Printf("error: %s", err) return 0, err } if n == 0 { break outer } r.RequestLine = *rl read += n r.state = StateHeaders case StateDone: break outer case StateHeaders: n, done, err := r.Headers.Parse(currentData) if err != nil { fmt.Printf("error: %s", err) return 0, err } read += n if done { remaining := currentData[n:] if bytes.HasPrefix(remaining, SEPARATOR) { read += len(SEPARATOR) } if getInt(r.Headers, "content-length", 0) == 0 { r.state = StateDone } else { r.state = StateBody } } else if n == 0 { break outer } case StateBody: length := getInt(r.Headers, "content-length", 0) if length == 0 { r.state = StateDone break } remaining := min(length-len(r.Body), len(currentData)) r.Body = append(r.Body, currentData[:remaining]...) read += remaining fmt.Printf("data: %d,%s", read, r.Body) if len(r.Body) == length { r.state = StateDone } default: panic("code bad") } } return read, nil } func (r *Request) done() bool { return r.state == StateDone } func RequestFromReader(reader io.Reader) (*Request, error) { request := newRequest() buf := make([]byte, 1024) bufLen := 0 for !request.done() { n, err := reader.Read(buf[bufLen:]) bufLen += n if n > 0 { readN, err := request.parse(buf[:bufLen]) if err != nil { return nil, err } copy(buf, buf[readN:bufLen]) bufLen -= readN } if err == io.EOF { break } if err != nil { return nil, err } } if !request.done() { return nil, fmt.Errorf("Incomplete request") } return request, nil }