Skip to content

Commit 07c1f64

Browse files
authored
Merge pull request #4 from arkahood/httpserver
initial server setup
2 parents 60a5c48 + a0286bc commit 07c1f64

File tree

6 files changed

+373
-9
lines changed

6 files changed

+373
-9
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: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package main
2+
3+
import (
4+
"io"
5+
"log"
6+
"os"
7+
"os/signal"
8+
"syscall"
9+
10+
"RAWHTTP/internal/request"
11+
"RAWHTTP/pkg/server"
12+
)
13+
14+
const port = 8080
15+
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 {
21+
return &server.HandlerError{
22+
StatusCode: 500,
23+
Message: "Internal Server Error: failed to write response",
24+
}
25+
}
26+
return nil
27+
}
28+
29+
func handler2(w io.Writer, req *request.Request) *server.HandlerError {
30+
// Write a simple response
31+
message := "Hello World2!\n"
32+
_, err := w.Write([]byte(message))
33+
if err != nil {
34+
return &server.HandlerError{
35+
StatusCode: 500,
36+
Message: "Internal Server Error: failed to write response",
37+
}
38+
}
39+
return nil
40+
}
41+
42+
func main() {
43+
r := server.NewRouter()
44+
server, err := server.Serve(r, port)
45+
46+
r.AddRoute("/", handler1)
47+
r.AddRoute("/second-route", handler2)
48+
49+
if err != nil {
50+
log.Fatalf("Error starting server: %v", err)
51+
}
52+
defer server.Close()
53+
log.Println("Server started on port", port)
54+
55+
sigChan := make(chan os.Signal, 1)
56+
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
57+
<-sigChan
58+
log.Println("Server gracefully stopped")
59+
}

internal/response/response.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package response
2+
3+
import (
4+
"RAWHTTP/internal/headers"
5+
"errors"
6+
"io"
7+
"strconv"
8+
)
9+
10+
type StatusCode int
11+
12+
const (
13+
StatusCodeOK StatusCode = iota
14+
StatusCodeClientErrors
15+
StatusCodeInternalServerError
16+
)
17+
18+
func WriteStatusLine(w io.Writer, statusCode StatusCode) error {
19+
var err error
20+
switch statusCode {
21+
case StatusCodeOK:
22+
_, err = w.Write([]byte("HTTP/1.1 200 OK\r\n"))
23+
case StatusCodeClientErrors:
24+
_, err = w.Write([]byte("HTTP/1.1 400 Bad Request\r\n"))
25+
case StatusCodeInternalServerError:
26+
_, err = w.Write([]byte("HTTP/1.1 500 Internal Server Error\r\n"))
27+
default:
28+
return errors.New("unsupported response type")
29+
}
30+
return err
31+
}
32+
func GetDefaultHeaders(contentLen int) headers.Headers {
33+
return headers.Headers{
34+
"content-length": strconv.Itoa(contentLen),
35+
"content-type": "text/plain",
36+
"connection": "close",
37+
}
38+
}
39+
40+
func WriteHeaders(w io.Writer, headers headers.Headers) error {
41+
var headerRes []byte
42+
for key, val := range headers {
43+
headerRes = append(headerRes, []byte(key+":"+val+"\r\n")...)
44+
}
45+
headerRes = append(headerRes, []byte("\r\n")...)
46+
_, err := w.Write(headerRes)
47+
return err
48+
}

0 commit comments

Comments
 (0)