Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
go-version: ['1.24']
os: [ubuntu-latest, windows-latest, darwin-latest]
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand Down
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ sooner rather than later
- [ ] chunked transfer responses (with channels)
- [ ] write cache headers on file handler responses
- [ ] CORS headers
- [ ] support CORS preflight requests (OPTIONS)
- [ ] custom reader type for request reading (alternative to bufio, greedy reader that reads until end of http request)

later:
Expand Down
139 changes: 139 additions & 0 deletions common/ascii/ascii.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package ascii

const (
NUL = byte(iota)
SOH
STX
ETX
EOT
ENQ
ACK
BEL
BS
HT
LF
VT
FF
CR
SO
SI
DLE
DC1
DC2
DC3
DC4
NAK
SYN
ETB
CAN
EM
SUB
ESC
FS // 0x1C File Separator
GS // 0x1D Group Separator
RS // 0x1E Record Separator
US // 0x1F Unit Separator

SPACE // 0x20
EXCL // 0x21 !
QUOTE // 0x22 "
HASH // 0x23 #
DOLLAR // 0x24 $
PERCENT // 0x25 %
AMPERSAND // 0x26 &
APOSTROPHE // 0x27 '
LPAREN // 0x28 (
RPAREN // 0x29 )
ASTERISK // 0x2A *
PLUS // 0x2B +
COMMA // 0x2C ,
MINUS // 0x2D -
DOT // 0x2E .
SLASH // 0x2F /

ZERO // 0x30
ONE // 0x31
TWO // 0x32
THREE // 0x33
FOUR // 0x34
FIVE // 0x35
SIX // 0x36
SEVEN // 0x37
EIGHT // 0x38
NINE // 0x39

COLON // 0x3A :
SEMICOLON // 0x3B ;
LT // 0x3C <
EQ // 0x3D =
GT // 0x3E >
QUESTION // 0x3F ?
AT // 0x40 @

A // 0x41
B // 0x42
C // 0x43
D // 0x44
E // 0x45
F // 0x46
G // 0x47
H // 0x48
I // 0x49
J // 0x4A
K // 0x4B
L // 0x4C
M // 0x4D
N // 0x4E
O // 0x4F
P // 0x50
Q // 0x51
R // 0x52
S // 0x53
T // 0x54
U // 0x55
V // 0x56
W // 0x57
X // 0x58
Y // 0x59
Z // 0x5A

LBRACKET // 0x5B [
BACKSLASH // 0x5C \
RBRACKET // 0x5D ]
CARET // 0x5E ^
UNDERSCORE // 0x5F _
GRAVE // 0x60 `

LC_A // 0x61
LC_B // 0x62
LC_C // 0x63
LC_D // 0x64
LC_E // 0x65
LC_F // 0x66
LC_G // 0x67
LC_H // 0x68
LC_I // 0x69
LC_J // 0x6A
LC_K // 0x6B
LC_L // 0x6C
LC_M // 0x6D
LC_N // 0x6E
LC_O // 0x6F
LC_P // 0x70
LC_Q // 0x71
LC_R // 0x72
LC_S // 0x73
LC_T // 0x74
LC_U // 0x75
LC_V // 0x76
LC_W // 0x77
LC_X // 0x78
LC_Y // 0x79
LC_Z // 0x7A

LBRACE // 0x7B {
PIPE // 0x7C |
RBRACE // 0x7D }
TILDE // 0x7E ~
DEL // 0x7F
)
66 changes: 54 additions & 12 deletions handlers/brotli.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,67 @@ func (b brotliHandler) HandleRequest(ctx http.Context) error {
if !reqAcceptsBrotli(ctx.Request) {
return nil
}
bbuf, err := castBody(ctx.Response.Body)
if err != nil {
return err
}
newBuf, err := b.compressBody(bbuf)
if err != nil {
return err

if c, ok := ctx.Response.Body.(chan http.StreamedResponseChunk); ok {
err := b.handleChannel(ctx, c)
if err != nil {
return err
}
} else { //NOT a channel, do normal stuff
bbuf, err := castBody(ctx.Response.Body)
if err != nil {
return err
}
newBuf, err := b.compressBody(bbuf)
if err != nil {
ctx.Response.AddHeader(http.Header{
Name: "Content-Length",
Value: strconv.Itoa(len(newBuf)),
})
return err
}
//assign body to response
ctx.Response.Body = newBuf
}

//assign body to response and set compression header
//set compression header
ctx.Response.AddHeader(http.Header{
Name: "Content-Encoding",
Value: "br",
})
ctx.Response.AddHeader(http.Header{
Name: "Content-Length",
Value: strconv.Itoa(len(newBuf)),
return nil
}

func (b brotliHandler) handleChannel(ctx http.Context, c chan http.StreamedResponseChunk) error {
tChan := make(chan http.StreamedResponseChunk, 1)
ctx.Response.Body = tChan
var newBuf bytes.Buffer
writer := brotli.NewWriterOptions(&newBuf, brotli.WriterOptions{
Quality: b.quality,
LGWin: 0,
})
ctx.Response.Body = newBuf
go func() {
defer close(tChan)
for chunk := range c {
if chunk.Err != nil {
tChan <- chunk
return
}
_, err := writer.Write(chunk.Data)
if err != nil {
tChan <- http.StreamedResponseChunk{Err: err}
return
}
}
err := writer.Close()
chunk := http.StreamedResponseChunk{}
if err != nil {
chunk.Err = err
} else {
chunk.Data = newBuf.Bytes()
}
tChan <- chunk
}()
return nil
}

Expand Down
40 changes: 26 additions & 14 deletions handlers/response_headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,41 @@ var ServerHeader = http.Header{
Value: fmt.Sprintf("%s/%s", SERVER_NAME, VERSION),
}

var timeFunc = time.Now

func ResponseHeadersHandler(ctx http.Context) error {
//Server
ctx.Response.AddHeader(ServerHeader)
//Date
ctx.Response.AddHeader(http.Header{
Name: "Date",
Value: common.ToHttpDateFormat(time.Now()),
Value: common.ToHttpDateFormat(timeFunc()),
})
if !ctx.Response.Headers.HasHeader("Content-Length") {
var length int
switch v := ctx.Response.Body.(type) {
case string:
length = len(v)
case []byte:
length = len(v)
default:
//TODO: figure out what to do
return nil
}
_, bodyIsChannel := ctx.Response.Body.(chan http.StreamedResponseChunk)
if bodyIsChannel {
//delete content-length, add transfer-encoding: chunked instead
delete(ctx.Response.Headers, "Content-Length")
ctx.Response.AddHeader(http.Header{
Name: "Content-Length",
Value: strconv.Itoa(length),
Name: "Transfer-Encoding",
Value: "chunked",
})
} else {
if !ctx.Response.Headers.HasHeader("Content-Length") {
var length int
switch v := ctx.Response.Body.(type) {
case string:
length = len(v)
case []byte:
length = len(v)
default:
//TODO: figure out what to do
return nil
}
ctx.Response.AddHeader(http.Header{
Name: "Content-Length",
Value: strconv.Itoa(length),
})
}
}

tryWriteConnectionHeader(ctx)
Expand Down
11 changes: 11 additions & 0 deletions handlers/time_helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build test

package handlers

import (
"time"
)

func SetTimeFunc(f func() time.Time) {
timeFunc = f
}
17 changes: 17 additions & 0 deletions http/headers.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package http

import (
"sort"
)

type Header struct {
Name string
Value string
Expand All @@ -15,3 +19,16 @@ func (m Headers) HasHeader(key string) bool {
}
return false
}

// Sorted returns Headers as a slice of Header, where the headers are sorted alphanumerically ascending by their Name property
func (m Headers) Sorted() []Header {
headers := make([]Header, 0, len(m))
for _, header := range m {
headers = append(headers, header)
}
// Sort by Name ascending
sort.Slice(headers, func(i, j int) bool {
return headers[i].Name < headers[j].Name
})
return headers
}
Loading
Loading