Files
test/internal/request/request.go
2026-04-04 21:27:36 +02:00

202 lines
3.6 KiB
Go

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
}