Skip to content

Commit a0286bc

Browse files
committed
LGTM
1 parent b63e412 commit a0286bc

File tree

5 files changed

+158
-42
lines changed

5 files changed

+158
-42
lines changed

.DS_Store

0 Bytes
Binary file not shown.

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
/idea
1+
/idea
2+
.DS_Store

README.md

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,119 @@
11
# RawHTTP
2-
HTTP 1.1 server from scratch
2+
A from-scratch HTTP/1.1 server implementation in Go, built directly on TCP sockets.
33

4-
## HTTP Message Structure
4+
## Project Overview
55

6-
According to RFC 7230, HTTP messages follow this structure:
6+
RawHTTP is an HTTP/1.1 server implementation that demonstrates low-level network programming concepts by building a complete HTTP server from first principles. Unlike frameworks that abstract away protocol details, this implementation provides direct insight into:
7+
8+
- Raw TCP socket handling
9+
- HTTP message parsing and generation
10+
- State machine-based request processing
11+
- Buffer management and streaming
12+
13+
explore how web servers work at the protocol level.
14+
15+
## HTTP Protocol Deep Dive
16+
17+
### HTTP Message Structure
18+
19+
According to RFC 7230, HTTP messages follow this precise structure:
720

821
```
922
start-line CRLF
1023
*( field-line CRLF )
11-
*( field-line CRLF )
12-
...
1324
CRLF
1425
[ message-body ]
1526
```
1627

1728
Where:
1829
- **start-line**: Request line (method, URI, version) or status line
19-
- **field-line**: HTTP headers (key-value pairs) (The RFC uses the term)
20-
- **CRLF**: Carriage return + line feed (`\r\n`)
21-
- **message-body**: Optional request/response body
30+
- **field-line**: HTTP headers (key-value pairs)
31+
- **CRLF**: Carriage return + line feed (`\r\n`) - critical for protocol compliance
32+
- **message-body**: Optional request/response body
33+
34+
### Request Message Anatomy
35+
36+
```
37+
GET /api/users HTTP/1.1\r\n ← Request Line
38+
Host: example.com\r\n ← Header Field
39+
User-Agent: RawHTTP/1.0\r\n ← Header Field
40+
Accept: application/json\r\n ← Header Field
41+
\r\n ← Header/Body Separator
42+
[optional message body] ← Body (for POST, PUT, etc.)
43+
```
44+
45+
#### Request Line Components
46+
47+
1. **HTTP Method**: Defines the action to be performed
48+
- `GET`: Retrieve data
49+
- `POST`: Submit data
50+
- `PUT`: Update/create resource
51+
- `DELETE`: Remove resource
52+
53+
2. **Request Target**: Identifies the resource
54+
- **origin-form**: `/path/to/resource?query=value`
55+
- **absolute-form**: `http://example.com/path`
56+
- **authority-form**: `example.com:80` (CONNECT only)
57+
- **asterisk-form**: `*` (OPTIONS only)
58+
59+
3. **HTTP Version**: Currently `HTTP/1.1`
60+
61+
### Response Message Anatomy
62+
63+
```
64+
HTTP/1.1 200 OK\r\n ← Status Line
65+
Content-Type: text/html\r\n ← Header Field
66+
Content-Length: 1234\r\n ← Header Field
67+
Connection: close\r\n ← Header Field
68+
\r\n ← Header/Body Separator
69+
<html>...</html> ← Body
70+
```
71+
72+
73+
### Header Field Theory
74+
75+
#### Field Name Constraints
76+
- **tchar**: `!#$%&'*+-.^_`|~` plus ALPHA and DIGIT
77+
- Case-insensitive (normalized to lowercase in our implementation)
78+
- No whitespace between field-name and colon
79+
80+
#### Field Value Processing
81+
- Leading/trailing whitespace is removed
82+
- Multiple values can be comma-separated
83+
84+
#### Critical Headers
85+
- **Host**: Required in HTTP/1.1, enables virtual hosting
86+
- **Content-Length**: **Byte count of message body**
87+
88+
## Running the Server
89+
90+
### Quick Start
91+
```bash
92+
# Clone the repository
93+
git clone https://github.com/arkahood/RawHTTP.git
94+
cd RawHTTP
95+
96+
# Run the HTTP server
97+
go run ./cmd/httpserver/main.go
98+
99+
# Server starts on port 8080
100+
# Visit http://localhost:8080 in your browser
101+
```
102+
103+
### Testing with curl
104+
```bash
105+
# Basic GET request
106+
curl -v http://localhost:8080/
107+
```
108+
109+
### Development Commands
110+
```bash
111+
# Run tests
112+
go test ./...
113+
114+
# Build binary
115+
go build -o httpserver cmd/httpserver/main.go
116+
117+
# View test coverage
118+
go test -cover ./...
119+
```

cmd/httpserver/main.go

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,34 @@
11
package main
22

33
import (
4-
"RAWHTTP/internal/request"
5-
"RAWHTTP/internal/server"
64
"io"
75
"log"
86
"os"
97
"os/signal"
108
"syscall"
9+
10+
"RAWHTTP/internal/request"
11+
"RAWHTTP/pkg/server"
1112
)
1213

1314
const port = 8080
1415

15-
func handler(w io.Writer, req *request.Request) *server.HandlerError {
16-
// Check if the request target is /yourproblem
17-
if req.RequestLine.RequestTarget == "/yourproblem" {
18-
return &server.HandlerError{
19-
StatusCode: 400,
20-
Message: "Your problem is not my problem\n",
21-
}
22-
}
23-
24-
if req.RequestLine.RequestTarget == "/myproblem" {
16+
func handler1(w io.Writer, req *request.Request) *server.HandlerError {
17+
// Write a simple response
18+
message := "Hello World!\n"
19+
_, err := w.Write([]byte(message))
20+
if err != nil {
2521
return &server.HandlerError{
2622
StatusCode: 500,
27-
Message: "My problem is not my problem\n",
28-
}
29-
}
30-
31-
if req.RequestLine.RequestTarget == "/route1" {
32-
message := "This is Route1\n"
33-
_, err := w.Write([]byte(message))
34-
if err != nil {
35-
return &server.HandlerError{
36-
StatusCode: 500,
37-
Message: "Internal Server Error: failed to write response",
38-
}
23+
Message: "Internal Server Error: failed to write response",
3924
}
40-
return nil
4125
}
26+
return nil
27+
}
4228

29+
func handler2(w io.Writer, req *request.Request) *server.HandlerError {
4330
// Write a simple response
44-
message := "Hello World!\n"
31+
message := "Hello World2!\n"
4532
_, err := w.Write([]byte(message))
4633
if err != nil {
4734
return &server.HandlerError{
@@ -53,7 +40,12 @@ func handler(w io.Writer, req *request.Request) *server.HandlerError {
5340
}
5441

5542
func main() {
56-
server, err := server.Serve(handler, port)
43+
r := server.NewRouter()
44+
server, err := server.Serve(r, port)
45+
46+
r.AddRoute("/", handler1)
47+
r.AddRoute("/second-route", handler2)
48+
5749
if err != nil {
5850
log.Fatalf("Error starting server: %v", err)
5951
}
Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
package server
22

33
import (
4-
"RAWHTTP/internal/request"
5-
"RAWHTTP/internal/response"
64
"bytes"
75
"errors"
86
"fmt"
97
"io"
108
"net"
119
"sync/atomic"
10+
11+
"RAWHTTP/internal/request"
12+
"RAWHTTP/internal/response"
1213
)
1314

1415
type Server struct {
1516
listener net.Listener
1617
serverIsClosed atomic.Bool
17-
handler Handler
18+
handler *Router
19+
}
20+
21+
type Router struct {
22+
routes map[string]Handler
23+
}
24+
25+
func (r *Router) AddRoute(route string, fn Handler) {
26+
r.routes[route] = fn
27+
}
28+
29+
func NewRouter() *Router {
30+
return &Router{
31+
routes: make(map[string]Handler),
32+
}
1833
}
1934

2035
type HandlerError struct {
@@ -58,14 +73,14 @@ func (hErr *HandlerError) WriteHandlerError(w io.Writer) error {
5873
return err
5974
}
6075

61-
func Serve(h Handler, port int) (*Server, error) {
76+
func Serve(r *Router, port int) (*Server, error) {
6277
lstnr, err := net.Listen("tcp", fmt.Sprint(":", port))
6378
if err != nil {
6479
return nil, errors.New("tcp error happened")
6580
}
6681
server := Server{
6782
listener: lstnr,
68-
handler: h,
83+
handler: r,
6984
}
7085

7186
go server.listen()
@@ -109,7 +124,17 @@ func (s *Server) handle(conn net.Conn) {
109124
buf := bytes.NewBuffer([]byte{})
110125

111126
// Call the handler function
112-
handlerErr := s.handler(buf, req)
127+
handler, exists := s.handler.routes[req.RequestLine.RequestTarget]
128+
if !exists {
129+
handlerErr := &HandlerError{
130+
StatusCode: 404,
131+
Message: "Not Found: " + req.RequestLine.RequestTarget,
132+
}
133+
handlerErr.WriteHandlerError(conn)
134+
return
135+
}
136+
137+
handlerErr := handler(buf, req)
113138

114139
// If the handler errors, write the error to the connection
115140
if handlerErr != nil {

0 commit comments

Comments
 (0)