boihttp/message.go

138 lines
2.8 KiB
Go
Raw Normal View History

2022-06-24 04:05:10 +00:00
// 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
}