138 lines
2.8 KiB
Go
138 lines
2.8 KiB
Go
|
// Copyright (c) 2022 Nadeen Udantha <me@nadeen.lk>. All rights reserved.
|
||
|
|
||
|
package boihttp
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"io"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
VersionHTTP1_0 = "HTTP/1.0"
|
||
|
VersionHTTP1_1 = "HTTP/1.1"
|
||
|
)
|
||
|
|
||
|
// https://datatracker.ietf.org/doc/html/rfc2616#section-4
|
||
|
type Message struct {
|
||
|
StatusCode int
|
||
|
Version, Method, Path, Status string
|
||
|
Headers
|
||
|
}
|
||
|
|
||
|
func (m *Message) SetStatus(code int, status string) {
|
||
|
m.Status = status
|
||
|
m.StatusCode = code
|
||
|
}
|
||
|
|
||
|
var EmptyBody io.Reader = &emptyBody{}
|
||
|
|
||
|
type emptyBody struct{}
|
||
|
|
||
|
func (l *emptyBody) Read(p []byte) (n int, err error) {
|
||
|
return 0, io.EOF
|
||
|
}
|
||
|
|
||
|
func (m *Message) body(r *bufio.Reader) io.Reader {
|
||
|
n := m.ContentLength()
|
||
|
if n == 0 {
|
||
|
return EmptyBody
|
||
|
}
|
||
|
return &io.LimitedReader{R: r, N: int64(n)}
|
||
|
}
|
||
|
|
||
|
func (m *Message) ReadRequest(r *bufio.Reader) error {
|
||
|
return m.read(r, true)
|
||
|
}
|
||
|
|
||
|
func (m *Message) ReadResponse(r *bufio.Reader) error {
|
||
|
return m.read(r, false)
|
||
|
}
|
||
|
|
||
|
func (m *Message) read(r *bufio.Reader, isRequest bool) error {
|
||
|
line, err := readline(r)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
parts := bytes.SplitN(line, []byte(" "), 3)
|
||
|
if len(parts) != 3 {
|
||
|
panic("wtf?")
|
||
|
}
|
||
|
if isRequest { // GET / HTTP/1.0
|
||
|
m.Method, m.Path, m.Version = string(parts[0]), string(parts[1]), string(parts[2])
|
||
|
} else { // HTTP/1.0 200 Noice
|
||
|
m.StatusCode, err = strconv.Atoi(string(parts[1]))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
m.Version, m.Status = string(parts[0]), string(parts[2])
|
||
|
}
|
||
|
m.Headers = make(Headers)
|
||
|
n := 0
|
||
|
for {
|
||
|
line, err := readline(r)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if len(line) == 0 {
|
||
|
break
|
||
|
}
|
||
|
parts = bytes.SplitN(line, []byte(": "), 2)
|
||
|
if len(parts) != 2 {
|
||
|
panic("wtf")
|
||
|
}
|
||
|
m.Add(string(parts[0]), string(parts[1]))
|
||
|
n++
|
||
|
if n >= 64 {
|
||
|
panic("wtf?")
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func readline(r *bufio.Reader) ([]byte, error) {
|
||
|
line, p, err := r.ReadLine()
|
||
|
if p || len(line) > 8192 {
|
||
|
panic("wtf?")
|
||
|
}
|
||
|
return line, err
|
||
|
}
|
||
|
|
||
|
const crlf = "\r\n"
|
||
|
|
||
|
func (m *Message) WriteRequest(r *bufio.Writer) error {
|
||
|
return m.write(r, true)
|
||
|
}
|
||
|
|
||
|
func (m *Message) WriteResponse(r *bufio.Writer) error {
|
||
|
return m.write(r, false)
|
||
|
}
|
||
|
|
||
|
func (m *Message) write(w *bufio.Writer, isRequest bool) error {
|
||
|
if isRequest { // GET / HTTP/1.0
|
||
|
w.WriteString(m.Method)
|
||
|
w.WriteByte(' ')
|
||
|
w.WriteString(m.Path)
|
||
|
w.WriteByte(' ')
|
||
|
w.WriteString(m.Version)
|
||
|
} else { // HTTP/1.0 200 Noice
|
||
|
w.WriteString(m.Version)
|
||
|
w.WriteByte(' ')
|
||
|
w.WriteString(strconv.Itoa(m.StatusCode))
|
||
|
w.WriteByte(' ')
|
||
|
w.WriteString(m.Status)
|
||
|
}
|
||
|
w.WriteString(crlf)
|
||
|
for k, vs := range m.Headers {
|
||
|
for _, v := range vs {
|
||
|
w.WriteString(k)
|
||
|
w.WriteString(": ")
|
||
|
w.WriteString(v)
|
||
|
w.WriteString(crlf)
|
||
|
}
|
||
|
}
|
||
|
_, err := w.WriteString(crlf)
|
||
|
return err
|
||
|
}
|