202 lines
3.6 KiB
Go
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
|
||
|
|
}
|