├── .gitignore
├── LICENSE.TXT
├── Makefile
├── README.md
├── bin
├── htdocs
│ └── test.html
└── server.config
└── src
├── ByteBuffer.cpp
├── ByteBuffer.h
├── Client.cpp
├── Client.h
├── HTTPMessage.cpp
├── HTTPMessage.h
├── HTTPRequest.cpp
├── HTTPRequest.h
├── HTTPResponse.cpp
├── HTTPResponse.h
├── HTTPServer.cpp
├── HTTPServer.h
├── MimeTypes.inc
├── Resource.cpp
├── Resource.h
├── ResourceHost.cpp
├── ResourceHost.h
├── SendQueueItem.h
├── convert_mimetypes.py
└── main.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/httpserver
2 | build/
3 | *DS_Store*
4 |
5 | .idea/*
6 | bin/htdocs/*
7 |
8 | *.o
9 |
--------------------------------------------------------------------------------
/LICENSE.TXT:
--------------------------------------------------------------------------------
1 | httpserver
2 | Copyright 2011-2025 Ramsey Kant
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for httpserver
2 | # (C) Ramsey Kant 2011-2025
3 |
4 | DEST = httpserver
5 | CLANG_FORMAT = clang-format
6 | LDFLAGS ?=
7 | CXXFLAGS ?=
8 | CXX = clang++
9 |
10 | CXXFLAGS += -Wall -Wextra -Wno-sign-compare -Wno-missing-field-initializers \
11 | -Wformat -Wformat=2 -Wimplicit-fallthrough \
12 | -march=x86-64-v2 -fPIE \
13 | -fexceptions \
14 | -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer \
15 | -fno-delete-null-pointer-checks -fno-strict-aliasing \
16 | -pedantic -std=c++23
17 |
18 | LDFLAGS += -fuse-ld=lld
19 |
20 | ifeq ($(DEBUG),1)
21 | CXXFLAGS += -O1 -g -DDEBUG=1 \
22 | -fasynchronous-unwind-tables \
23 | -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG \
24 | -fstack-protector-all
25 | else
26 | CXXFLAGS += -O2 -DNDEBUG -flto=auto
27 | LDFLAGS += -flto=auto
28 | endif
29 |
30 | SOURCES = $(sort $(wildcard src/*.cpp))
31 | OBJECTS = $(SOURCES:.cpp=.o)
32 | CLEANFILES = $(OBJECTS) bin/$(DEST)
33 |
34 | all: make-src
35 |
36 | make-src: $(DEST)
37 |
38 | $(DEST): $(OBJECTS)
39 | $(CXX) $(LDFLAGS) $(CXXFLAGS) $(OBJECTS) -o bin/$@
40 |
41 | clean:
42 | rm -f $(CLEANFILES)
43 |
44 | debug:
45 | DEBUG=1 $(MAKE) all
46 |
47 | asan:
48 | CXXFLAGS=-fsanitize=address DEBUG=1 $(MAKE) all
49 |
50 | .PHONY: all make-src clean debug asan
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HTTP Server
2 |
3 | Ramsey Kant
4 | https://github.com/RamseyK/httpserver
5 |
6 | A high performance, single threaded, HTTP/1.1 server written in C++ to serve as a kqueue socket management and HTTP/1.1 protocol learning tool on BSD systems
7 |
8 | ## Features
9 | * Clean, documented code
10 | * Efficient socket management with kqueue
11 | * Easy to understand HTTP protocol parser (from my [ByteBuffer](https://github.com/RamseyK/ByteBufferCpp) project)
12 | * Tested on FreeBSD and macOS
13 |
14 | ## Compiling
15 | * Only BSD based systems are supported. Linux _may_ work when [libkqueue is compiled from Github sources](https://github.com/mheily/libkqueue) and linked, but this is unsupported.
16 | * On FreeBSD, compile with gmake
17 |
18 | ## Usage
19 |
20 | ```
21 |
22 | $ cat server.config
23 | vhost=10.0.10.86,acme.local
24 | port=8080
25 | diskpath=./htdocs
26 |
27 | $ ./httpserver
28 | Primary port: 8080, disk path: ./htdocs
29 | vhost: 10.0.10.86
30 | vhost: acme.local
31 | Server ready. Listening on port 8080...
32 | ```
33 |
34 | ## License
35 | See LICENSE.TXT
36 |
--------------------------------------------------------------------------------
/bin/htdocs/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Test Title
5 |
6 |
7 |
8 |
9 | Hello World!
10 |
11 |
12 |
--------------------------------------------------------------------------------
/bin/server.config:
--------------------------------------------------------------------------------
1 | # Hosts to respond to (localhost/127.0.0.1 implied)
2 | vhost=10.0.10.86,acme.local
3 | port=8080
4 | diskpath=./htdocs
5 |
6 | # Optional - uid/gid to "drop" to with setuid/setgid after bind() so the program doesn't have to remain as root
7 | # Default 0 because dropping to root makes no sense
8 | drop_uid=0
9 | drop_gid=0
10 |
--------------------------------------------------------------------------------
/src/ByteBuffer.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | ByteBuffer
3 | ByteBuffer.cpp
4 | Copyright 2011-2025 Ramsey Kant
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 |
18 | Modified 2015 by Ashley Davis (SgtCoDFish)
19 | */
20 |
21 | #include "ByteBuffer.h"
22 |
23 | #ifdef BB_UTILITY
24 | #include
25 | #include
26 | #include
27 | #endif
28 |
29 | #ifdef BB_USE_NS
30 | namespace bb {
31 | #endif
32 |
33 | /**
34 | * ByteBuffer constructor
35 | * Reserves specified size in internal vector
36 | *
37 | * @param size Size (in bytes) of space to preallocate internally. Default is set in BB_DEFAULT_SIZE
38 | */
39 | ByteBuffer::ByteBuffer(uint32_t size) {
40 | buf.reserve(size);
41 | clear();
42 | }
43 |
44 | /**
45 | * ByteBuffer constructor
46 | * Consume an entire byte array of length len in the ByteBuffer
47 | *
48 | * @param arr byte array of data (should be of length len)
49 | * @param size Size of space to allocate
50 | */
51 | ByteBuffer::ByteBuffer(const uint8_t* arr, uint32_t size) {
52 | // If the provided array is NULL, allocate a blank buffer of the provided size
53 | if (arr == nullptr) {
54 | buf.reserve(size);
55 | clear();
56 | } else { // Consume the provided array
57 | buf.reserve(size);
58 | clear();
59 | putBytes(arr, size);
60 | }
61 | }
62 |
63 | /**
64 | * Bytes Remaining
65 | * Returns the number of bytes from the current read position till the end of the buffer
66 | *
67 | * @return Number of bytes from rpos to the end (size())
68 | */
69 | uint32_t ByteBuffer::bytesRemaining() const {
70 | return size() - rpos;
71 | }
72 |
73 | /**
74 | * Clear
75 | * Clears out all data from the internal vector (original preallocated size remains), resets the positions to 0
76 | */
77 | void ByteBuffer::clear() {
78 | rpos = 0;
79 | wpos = 0;
80 | buf.clear();
81 | }
82 |
83 | /**
84 | * Clone
85 | * Allocate an exact copy of the ByteBuffer on the heap and return a pointer
86 | *
87 | * @return A pointer to the newly cloned ByteBuffer. NULL if no more memory available
88 | */
89 | std::unique_ptr ByteBuffer::clone() {
90 | auto ret = std::make_unique(buf.size());
91 |
92 | // Copy data
93 | for (uint32_t i = 0; i < buf.size(); i++) {
94 | ret->put(get(i));
95 | }
96 |
97 | // Reset positions
98 | ret->setReadPos(0);
99 | ret->setWritePos(0);
100 |
101 | return ret;
102 | }
103 |
104 | /**
105 | * Equals, test for data equivilancy
106 | * Compare this ByteBuffer to another by looking at each byte in the internal buffers and making sure they are the same
107 | *
108 | * @param other A pointer to a ByteBuffer to compare to this one
109 | * @return True if the internal buffers match. False if otherwise
110 | */
111 | bool ByteBuffer::equals(const ByteBuffer* other) const {
112 | // If sizes aren't equal, they can't be equal
113 | if (size() != other->size())
114 | return false;
115 |
116 | // Compare byte by byte
117 | uint32_t len = size();
118 | for (uint32_t i = 0; i < len; i++) {
119 | if (get(i) != other->get(i))
120 | return false;
121 | }
122 |
123 | return true;
124 | }
125 |
126 | /**
127 | * Resize
128 | * Reallocates memory for the internal buffer of size newSize. Read and write positions will also be reset
129 | *
130 | * @param newSize The amount of memory to allocate
131 | */
132 | void ByteBuffer::resize(uint32_t newSize) {
133 | buf.resize(newSize);
134 | rpos = 0;
135 | wpos = 0;
136 | }
137 |
138 | /**
139 | * Size
140 | * Returns the size of the internal buffer...not necessarily the length of bytes used as data!
141 | *
142 | * @return size of the internal buffer
143 | */
144 | uint32_t ByteBuffer::size() const {
145 | return buf.size();
146 | }
147 |
148 | // Replacement
149 |
150 | /**
151 | * Replace
152 | * Replace occurrence of a particular byte, key, with the byte rep
153 | *
154 | * @param key Byte to find for replacement
155 | * @param rep Byte to replace the found key with
156 | * @param start Index to start from. By default, start is 0
157 | * @param firstOccurrenceOnly If true, only replace the first occurrence of the key. If false, replace all occurrences. False by default
158 | */
159 | void ByteBuffer::replace(uint8_t key, uint8_t rep, uint32_t start, bool firstOccurrenceOnly) {
160 | uint32_t len = buf.size();
161 | for (uint32_t i = start; i < len; i++) {
162 | uint8_t data = read(i);
163 | // Wasn't actually found, bounds of buffer were exceeded
164 | if ((key != 0) && (data == 0))
165 | break;
166 |
167 | // Key was found in array, perform replacement
168 | if (data == key) {
169 | buf[i] = rep;
170 | if (firstOccurrenceOnly)
171 | return;
172 | }
173 | }
174 | }
175 |
176 | // Read Functions
177 |
178 | uint8_t ByteBuffer::peek() const {
179 | return read(rpos);
180 | }
181 |
182 | uint8_t ByteBuffer::get() {
183 | return read();
184 | }
185 |
186 | uint8_t ByteBuffer::get(uint32_t index) const {
187 | return read(index);
188 | }
189 |
190 | void ByteBuffer::getBytes(uint8_t* const out_buf, uint32_t out_len) {
191 | for (uint32_t i = 0; i < out_len; i++) {
192 | out_buf[i] = read();
193 | }
194 | }
195 |
196 | char ByteBuffer::getChar() {
197 | return read();
198 | }
199 |
200 | char ByteBuffer::getChar(uint32_t index) const {
201 | return read(index);
202 | }
203 |
204 | double ByteBuffer::getDouble() {
205 | return read();
206 | }
207 |
208 | double ByteBuffer::getDouble(uint32_t index) const {
209 | return read(index);
210 | }
211 |
212 | float ByteBuffer::getFloat() {
213 | return read();
214 | }
215 |
216 | float ByteBuffer::getFloat(uint32_t index) const {
217 | return read(index);
218 | }
219 |
220 | uint32_t ByteBuffer::getInt() {
221 | return read();
222 | }
223 |
224 | uint32_t ByteBuffer::getInt(uint32_t index) const {
225 | return read(index);
226 | }
227 |
228 | uint64_t ByteBuffer::getLong() {
229 | return read();
230 | }
231 |
232 | uint64_t ByteBuffer::getLong(uint32_t index) const {
233 | return read(index);
234 | }
235 |
236 | uint16_t ByteBuffer::getShort() {
237 | return read();
238 | }
239 |
240 | uint16_t ByteBuffer::getShort(uint32_t index) const {
241 | return read(index);
242 | }
243 |
244 |
245 | // Write Functions
246 |
247 | void ByteBuffer::put(const ByteBuffer* src) {
248 | uint32_t len = src->size();
249 | for (uint32_t i = 0; i < len; i++)
250 | append(src->get(i));
251 | }
252 |
253 | void ByteBuffer::put(uint8_t b) {
254 | append(b);
255 | }
256 |
257 | void ByteBuffer::put(uint8_t b, uint32_t index) {
258 | insert(b, index);
259 | }
260 |
261 | void ByteBuffer::putBytes(const uint8_t* const b, uint32_t len) {
262 | // Insert the data one byte at a time into the internal buffer at position i+starting index
263 | for (uint32_t i = 0; i < len; i++)
264 | append(b[i]);
265 | }
266 |
267 | void ByteBuffer::putBytes(const uint8_t* const b, uint32_t len, uint32_t index) {
268 | wpos = index;
269 |
270 | // Insert the data one byte at a time into the internal buffer at position i+starting index
271 | for (uint32_t i = 0; i < len; i++)
272 | append(b[i]);
273 | }
274 |
275 | void ByteBuffer::putChar(char value) {
276 | append(value);
277 | }
278 |
279 | void ByteBuffer::putChar(char value, uint32_t index) {
280 | insert(value, index);
281 | }
282 |
283 | void ByteBuffer::putDouble(double value) {
284 | append(value);
285 | }
286 |
287 | void ByteBuffer::putDouble(double value, uint32_t index) {
288 | insert(value, index);
289 | }
290 | void ByteBuffer::putFloat(float value) {
291 | append(value);
292 | }
293 |
294 | void ByteBuffer::putFloat(float value, uint32_t index) {
295 | insert(value, index);
296 | }
297 |
298 | void ByteBuffer::putInt(uint32_t value) {
299 | append(value);
300 | }
301 |
302 | void ByteBuffer::putInt(uint32_t value, uint32_t index) {
303 | insert(value, index);
304 | }
305 |
306 | void ByteBuffer::putLong(uint64_t value) {
307 | append(value);
308 | }
309 |
310 | void ByteBuffer::putLong(uint64_t value, uint32_t index) {
311 | insert(value, index);
312 | }
313 |
314 | void ByteBuffer::putShort(uint16_t value) {
315 | append(value);
316 | }
317 |
318 | void ByteBuffer::putShort(uint16_t value, uint32_t index) {
319 | insert(value, index);
320 | }
321 |
322 | // Utility Functions
323 | #ifdef BB_UTILITY
324 | void ByteBuffer::setName(std::string_view n) {
325 | name = n;
326 | }
327 |
328 | std::string ByteBuffer::getName() const {
329 | return name;
330 | }
331 |
332 | void ByteBuffer::printInfo() const {
333 | uint32_t length = buf.size();
334 | std::cout << "ByteBuffer " << name << " Length: " << length << ". Info Print" << std::endl;
335 | }
336 |
337 | void ByteBuffer::printAH() const {
338 | uint32_t length = buf.size();
339 | std::cout << "ByteBuffer " << name << " Length: " << length << ". ASCII & Hex Print" << std::endl;
340 | for (uint32_t i = 0; i < length; i++) {
341 | std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << int(buf[i]) << " ";
342 | }
343 | std::printf("\n");
344 | for (uint32_t i = 0; i < length; i++) {
345 | std::cout << (char)buf[i] << " ";
346 | }
347 | std::cout << std::endl;
348 | }
349 |
350 | void ByteBuffer::printAscii() const {
351 | uint32_t length = buf.size();
352 | std::cout << "ByteBuffer " << name << " Length: " << length << ". ASCII Print" << std::endl;
353 | for (uint32_t i = 0; i < length; i++) {
354 | std::cout << (char)buf[i] << " ";
355 | }
356 | std::cout << std::endl;
357 | }
358 |
359 | void ByteBuffer::printHex() const {
360 | uint32_t length = buf.size();
361 | std::cout << "ByteBuffer " << name << " Length: " << length << ". Hex Print" << std::endl;
362 | for (uint32_t i = 0; i < length; i++) {
363 | std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << int(buf[i]) << " ";
364 | }
365 | std::cout << std::endl;
366 | }
367 |
368 | void ByteBuffer::printPosition() const {
369 | uint32_t length = buf.size();
370 | std::cout << "ByteBuffer " << name << " Length: " << length << " Read Pos: " << rpos << ". Write Pos: "
371 | << wpos << std::endl;
372 | }
373 |
374 | #endif // BB_UTILITY
375 |
376 | #ifdef BB_USE_NS
377 | }
378 | #endif
379 |
--------------------------------------------------------------------------------
/src/ByteBuffer.h:
--------------------------------------------------------------------------------
1 | /**
2 | ByteBuffer
3 | ByteBuffer.h
4 | Copyright 2011-2015 Ramsey Kant
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 |
18 | Modified 2015 by Ashley Davis (SgtCoDFish)
19 | */
20 |
21 | #ifndef _BYTEBUFFER_H_
22 | #define _BYTEBUFFER_H_
23 |
24 | #include
25 | #include
26 | #include
27 |
28 | #ifdef BB_UTILITY
29 | #include
30 | #endif
31 |
32 | // Default number of bytes to allocate in the backing buffer if no size is provided
33 | constexpr uint32_t BB_DEFAULT_SIZE = 4096;
34 |
35 | #ifdef BB_USE_NS
36 | namespace bb {
37 | #endif
38 |
39 | class ByteBuffer {
40 | public:
41 | explicit ByteBuffer(uint32_t size = BB_DEFAULT_SIZE);
42 | explicit ByteBuffer(const uint8_t* arr, uint32_t size);
43 | virtual ~ByteBuffer() = default;
44 |
45 | uint32_t bytesRemaining() const; // Number of bytes from the current read position till the end of the buffer
46 | void clear(); // Clear our the vector and reset read and write positions
47 | std::unique_ptr clone(); // Return a new instance of a ByteBuffer with the exact same contents and the same state (rpos, wpos)
48 | bool equals(const ByteBuffer* other) const; // Compare if the contents are equivalent
49 | void resize(uint32_t newSize);
50 | uint32_t size() const; // Size of internal vector
51 |
52 | // Basic Searching (Linear)
53 | template int32_t find(T key, uint32_t start=0) {
54 | int32_t ret = -1;
55 | uint32_t len = buf.size();
56 | for (uint32_t i = start; i < len; i++) {
57 | T data = read(i);
58 | // Wasn't actually found, bounds of buffer were exceeded
59 | if ((key != 0) && (data == 0))
60 | break;
61 |
62 | // Key was found in array
63 | if (data == key) {
64 | ret = i;
65 | break;
66 | }
67 | }
68 | return ret;
69 | }
70 |
71 | // Replacement
72 | void replace(uint8_t key, uint8_t rep, uint32_t start = 0, bool firstOccurrenceOnly=false);
73 |
74 | // Read
75 |
76 | uint8_t peek() const; // Relative peek. Reads and returns the next byte in the buffer from the current position but does not increment the read position
77 | uint8_t get(); // Relative get method. Reads the byte at the buffers current position then increments the position
78 | uint8_t get(uint32_t index) const; // Absolute get method. Read byte at index
79 | void getBytes(uint8_t* const out_buf, uint32_t out_len); // Absolute read into array buf of length len
80 | char getChar(); // Relative
81 | char getChar(uint32_t index) const; // Absolute
82 | double getDouble();
83 | double getDouble(uint32_t index) const;
84 | float getFloat();
85 | float getFloat(uint32_t index) const;
86 | uint32_t getInt();
87 | uint32_t getInt(uint32_t index) const;
88 | uint64_t getLong();
89 | uint64_t getLong(uint32_t index) const;
90 | uint16_t getShort();
91 | uint16_t getShort(uint32_t index) const;
92 |
93 | // Write
94 |
95 | void put(const ByteBuffer* src); // Relative write of the entire contents of another ByteBuffer (src)
96 | void put(uint8_t b); // Relative write
97 | void put(uint8_t b, uint32_t index); // Absolute write at index
98 | void putBytes(const uint8_t* const b, uint32_t len); // Relative write
99 | void putBytes(const uint8_t* const b, uint32_t len, uint32_t index); // Absolute write starting at index
100 | void putChar(char value); // Relative
101 | void putChar(char value, uint32_t index); // Absolute
102 | void putDouble(double value);
103 | void putDouble(double value, uint32_t index);
104 | void putFloat(float value);
105 | void putFloat(float value, uint32_t index);
106 | void putInt(uint32_t value);
107 | void putInt(uint32_t value, uint32_t index);
108 | void putLong(uint64_t value);
109 | void putLong(uint64_t value, uint32_t index);
110 | void putShort(uint16_t value);
111 | void putShort(uint16_t value, uint32_t index);
112 |
113 | // Buffer Position Accessors & Mutators
114 |
115 | void setReadPos(uint32_t r) {
116 | rpos = r;
117 | }
118 |
119 | uint32_t getReadPos() const {
120 | return rpos;
121 | }
122 |
123 | void setWritePos(uint32_t w) {
124 | wpos = w;
125 | }
126 |
127 | uint32_t getWritePos() const {
128 | return wpos;
129 | }
130 |
131 | // Utility Functions
132 | #ifdef BB_UTILITY
133 | void setName(std::string_view n);
134 | std::string getName() const;
135 | void printInfo() const;
136 | void printAH() const;
137 | void printAscii() const;
138 | void printHex() const;
139 | void printPosition() const;
140 | #endif
141 |
142 | private:
143 | uint32_t rpos = 0;
144 | uint32_t wpos = 0;
145 | std::vector buf;
146 |
147 | #ifdef BB_UTILITY
148 | std::string name = "";
149 | #endif
150 |
151 | template T read() {
152 | T data = read(rpos);
153 | rpos += sizeof(T);
154 | return data;
155 | }
156 |
157 | template T read(uint32_t index) const {
158 | if (index + sizeof(T) <= buf.size())
159 | return *((T* const)&buf[index]);
160 | return 0;
161 | }
162 |
163 | template void append(T data) {
164 | uint32_t s = sizeof(data);
165 |
166 | if (size() < (wpos + s))
167 | buf.resize(wpos + s);
168 | memcpy(&buf[wpos], (uint8_t*)&data, s);
169 |
170 | wpos += s;
171 | }
172 |
173 | template void insert(T data, uint32_t index) {
174 | if ((index + sizeof(data)) > size()) {
175 | buf.resize(size() + (index + sizeof(data)));
176 | }
177 |
178 | memcpy(&buf[index], (uint8_t*)&data, sizeof(data));
179 | wpos = index + sizeof(data);
180 | }
181 | };
182 |
183 | #ifdef BB_USE_NS
184 | }
185 | #endif
186 |
187 | #endif
188 |
--------------------------------------------------------------------------------
/src/Client.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | httpserver
3 | Client.cpp
4 | Copyright 2011-2025 Ramsey Kant
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 |
19 | #include "Client.h"
20 |
21 | Client::Client(int fd, sockaddr_in addr) : socketDesc(fd), clientAddr(addr) {
22 | }
23 |
24 | Client::~Client() {
25 | clearSendQueue();
26 | }
27 |
28 | /**
29 | * Add to Send Queue
30 | * Adds a SendQueueItem object to the end of this client's send queue
31 | */
32 | void Client::addToSendQueue(SendQueueItem* item) {
33 | sendQueue.push(item);
34 | }
35 |
36 | /**
37 | * sendQueueSize
38 | * Returns the current number of SendQueueItem's in the send queue
39 | *
40 | * @return Integer representing number of items in this clients send queue
41 | */
42 | uint32_t Client::sendQueueSize() const {
43 | return sendQueue.size();
44 | }
45 |
46 | /**
47 | * Next from Send Queue
48 | * Returns the current SendQueueItem object to be sent to the client
49 | *
50 | * @return SendQueueItem object containing the data to send and current offset
51 | */
52 | SendQueueItem* Client::nextInSendQueue() {
53 | if (sendQueue.empty())
54 | return nullptr;
55 |
56 | return sendQueue.front();
57 | }
58 |
59 | /**
60 | * Dequeue from Send Queue
61 | * Deletes and dequeues first item in the queue
62 | */
63 | void Client::dequeueFromSendQueue() {
64 | SendQueueItem* item = nextInSendQueue();
65 | if (item != nullptr) {
66 | sendQueue.pop();
67 | delete item;
68 | }
69 | }
70 |
71 | /**
72 | * Clear Send Queue
73 | * Clears out the send queue for the client, deleting all internal SendQueueItem objects
74 | */
75 | void Client::clearSendQueue() {
76 | while (!sendQueue.empty()) {
77 | delete sendQueue.front();
78 | sendQueue.pop();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Client.h:
--------------------------------------------------------------------------------
1 | /**
2 | httpserver
3 | Client.h
4 | Copyright 2011-2025 Ramsey Kant
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 |
19 | #ifndef _CLIENT_H_
20 | #define _CLIENT_H_
21 |
22 | #include "SendQueueItem.h"
23 |
24 | #include
25 | #include
26 | #include
27 |
28 | class Client {
29 | int32_t socketDesc; // Socket Descriptor
30 | sockaddr_in clientAddr;
31 |
32 | std::queue sendQueue;
33 |
34 | public:
35 | Client(int fd, sockaddr_in addr);
36 | ~Client();
37 | Client& operator=(Client const&) = delete; // Copy assignment
38 | Client(Client &&) = delete; // Move
39 | Client& operator=(Client &&) = delete; // Move assignment
40 |
41 | sockaddr_in getClientAddr() const {
42 | return clientAddr;
43 | }
44 |
45 | int32_t getSocket() const {
46 | return socketDesc;
47 | }
48 |
49 | char* getClientIP() {
50 | return inet_ntoa(clientAddr.sin_addr);
51 | }
52 |
53 | void addToSendQueue(SendQueueItem* item);
54 | uint32_t sendQueueSize() const;
55 | SendQueueItem* nextInSendQueue();
56 | void dequeueFromSendQueue();
57 | void clearSendQueue();
58 | };
59 |
60 | #endif
61 |
--------------------------------------------------------------------------------
/src/HTTPMessage.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | ByteBuffer
3 | HTTPMessage.cpp
4 | Copyright 2011-2025 Ramsey Kant
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 |
19 | #include "HTTPMessage.h"
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | HTTPMessage::HTTPMessage() : ByteBuffer(4096) {
27 | }
28 |
29 | HTTPMessage::HTTPMessage(std::string const& sData) : ByteBuffer(sData.size() + 1) {
30 | putBytes((const uint8_t* const)sData.c_str(), sData.size() + 1);
31 | }
32 |
33 | HTTPMessage::HTTPMessage(const uint8_t* pData, uint32_t len) : ByteBuffer(pData, len) {
34 | }
35 |
36 | /**
37 | * Put Line
38 | * Append a line (string) to the backing ByteBuffer at the current position
39 | *
40 | * @param str String to put into the ByteBuffer
41 | * @param crlf_end If true (default), end the line with a \r\n
42 | */
43 | void HTTPMessage::putLine(std::string str, bool crlf_end) {
44 | // Terminate with crlf if flag set
45 | if (crlf_end)
46 | str += "\r\n";
47 |
48 | // Put the entire contents of str into the buffer
49 | putBytes((const uint8_t* const)str.c_str(), str.size());
50 | }
51 |
52 | /**
53 | * Put Headers
54 | * Write all headers currently in the 'headers' map to the ByteBuffer.
55 | * 'Header: value'
56 | */
57 | void HTTPMessage::putHeaders() {
58 | for (auto const &[key, value] : headers) {
59 | putLine(std::format("{}: {}", key, value), true);
60 | }
61 |
62 | // End with a blank line
63 | putLine();
64 | }
65 |
66 | /**
67 | * Get Line
68 | * Retrive the entire contents of a line: string from current position until CR or LF, whichever comes first, then increment the read position
69 | * until it's past the last CR or LF in the line
70 | *
71 | * @return Contents of the line in a string (without CR or LF)
72 | */
73 | std::string HTTPMessage::getLine() {
74 | std::string ret = "";
75 | int32_t startPos = getReadPos();
76 | bool newLineReached = false;
77 | char c = 0;
78 |
79 | // Append characters to the return std::string until we hit the end of the buffer, a CR (13) or LF (10)
80 | for (uint32_t i = startPos; i < size(); i++) {
81 | // If the next byte is a \r or \n, we've reached the end of the line and should break out of the loop
82 | c = peek();
83 | if ((c == 13) || (c == 10)) {
84 | newLineReached = true;
85 | break;
86 | }
87 |
88 | // Otherwise, append the next character to the std::string
89 | ret += getChar();
90 | }
91 |
92 | // If a line termination was never reached, discard the result and conclude there are no more lines to parse
93 | if (!newLineReached) {
94 | setReadPos(startPos); // Reset the position to before the last line read that we are now discarding
95 | ret = "";
96 | return ret;
97 | }
98 |
99 | // Increment the read position until the end of a CR or LF chain, so the read position will then point to the next line
100 | // Also, only read a maximum of 2 characters so as to not skip a blank line that is only \r\n
101 | uint32_t k = 0;
102 | for (uint32_t i = getReadPos(); i < size(); i++) {
103 | if (k++ >= 2)
104 | break;
105 | c = getChar();
106 | if ((c != 13) && (c != 10)) {
107 | // Set the Read position back one because the retrived character wasn't a LF or CR
108 | setReadPos(getReadPos() - 1);
109 | break;
110 | }
111 | }
112 |
113 | return ret;
114 | }
115 |
116 | /**
117 | * getStrElement
118 | * Get a token from the current buffer, stopping at the delimiter. Returns the token as a string
119 | *
120 | * @param delim The delimiter to stop at when retriving the element. By default, it's a space
121 | * @return Token found in the buffer. Empty if delimiter wasn't reached
122 | */
123 | std::string HTTPMessage::getStrElement(char delim) {
124 | int32_t startPos = getReadPos();
125 | if (startPos < 0)
126 | return "";
127 |
128 | int32_t endPos = find(delim, startPos);
129 | if (endPos < 0)
130 | return "";
131 |
132 | if (startPos > endPos)
133 | return "";
134 |
135 | // Calculate the size based on the found ending position
136 | uint32_t size = (endPos + 1) - startPos;
137 | if (size <= 0)
138 | return "";
139 |
140 | // Grab the std::string from the ByteBuffer up to the delimiter
141 | auto str = std::make_unique(size);
142 | getBytes((uint8_t*)str.get(), size);
143 | str[size - 1] = 0x00; // NULL termination
144 | std::string ret = str.get();
145 |
146 | // Increment the read position PAST the delimiter
147 | setReadPos(endPos + 1);
148 |
149 | return ret;
150 | }
151 |
152 | /**
153 | * Parse Headers
154 | * When an HTTP message (request & response) has reached the point where headers are present, this method
155 | * should be called to parse and populate the internal map of headers.
156 | * Parse headers will move the read position past the blank line that signals the end of the headers
157 | */
158 | void HTTPMessage::parseHeaders() {
159 | std::string hline = "";
160 | std::string app = "";
161 |
162 | // Get the first header
163 | hline = getLine();
164 |
165 | // Keep pulling headers until a blank line has been reached (signaling the end of headers)
166 | while (hline.size() > 0) {
167 | // Case where values are on multiple lines ending with a comma
168 | app = hline;
169 | while (app[app.size() - 1] == ',') {
170 | app = getLine();
171 | hline += app;
172 | }
173 |
174 | addHeader(hline);
175 | hline = getLine();
176 | }
177 | }
178 |
179 | /**
180 | * Parse Body
181 | * Parses everything after the headers section of an HTTP message. Handles chuncked responses/requests
182 | *
183 | * @return True if successful. False on error, parseErrorStr is set with a reason
184 | */
185 | bool HTTPMessage::parseBody() {
186 | // Content-Length should exist (size of the Body data) if there is body data
187 | std::string hlenstr = "";
188 | uint32_t contentLen = 0;
189 | hlenstr = getHeaderValue("Content-Length");
190 |
191 | // No body data to read:
192 | if (hlenstr.empty())
193 | return true;
194 |
195 | contentLen = atoi(hlenstr.c_str());
196 |
197 | // contentLen should NOT exceed the remaining number of bytes in the buffer
198 | // Add 1 to bytesRemaining so it includes the byte at the current read position
199 | if (contentLen > bytesRemaining() + 1) {
200 | // If it exceeds, read only up to the number of bytes remaining
201 | // dataLen = bytesRemaining();
202 |
203 | // If it exceeds, there's a potential security issue and we can't reliably parse
204 | parseErrorStr = std::format("Content-Length ({}) is greater than remaining bytes ({})", hlenstr, bytesRemaining());
205 | return false;
206 | } else if (contentLen == 0) {
207 | // Nothing to read, which is fine
208 | return true;
209 | } else {
210 | // Otherwise, we can probably trust Content-Length is valid and read the specificed number of bytes
211 | dataLen = contentLen;
212 | }
213 |
214 | // Create a big enough buffer to store the data
215 | uint32_t dIdx = 0;
216 | uint32_t s = size();
217 | if (s > dataLen) {
218 | parseErrorStr = std::format("ByteBuffer size of {} is greater than dataLen {}", s, dataLen);
219 | return false;
220 | }
221 |
222 | data = new uint8_t[dataLen];
223 |
224 | // Grab all the bytes from the current position to the end
225 | for (uint32_t i = getReadPos(); i < s; i++) {
226 | data[dIdx] = get(i);
227 | dIdx++;
228 | }
229 |
230 | // We could handle chunked Request/Response parsing (with footers) here, but, we won't.
231 |
232 | return true;
233 | }
234 |
235 | /**
236 | * Add Header to the Map from string
237 | * Takes a formatted header string "Header: value", parse it, and put it into the std::map as a key,value pair.
238 | *
239 | * @param string containing formatted header: value
240 | */
241 | void HTTPMessage::addHeader(std::string const& line) {
242 | size_t kpos = line.find(':');
243 | if (kpos == std::string::npos) {
244 | std::cout << "Could not addHeader: " << line << std::endl;
245 | return;
246 | }
247 | // We're choosing to reject HTTP Header keys longer than 32 characters
248 | if (kpos > 32)
249 | return;
250 |
251 | std::string key = line.substr(0, kpos);
252 | if (key.empty())
253 | return;
254 |
255 | int32_t value_len = line.size() - kpos - 1;
256 | if (value_len <= 0)
257 | return;
258 |
259 | // We're choosing to reject HTTP header values longer than 4kb
260 | if (value_len > 4096)
261 | return;
262 |
263 | std::string value = line.substr(kpos + 1, value_len);
264 |
265 | // Skip all leading spaces in the value
266 | int32_t i = 0;
267 | while (i < value.size() && value.at(i) == 0x20) {
268 | i++;
269 | }
270 | value = value.substr(i, value.size());
271 | if (value.empty())
272 | return;
273 |
274 | // Add header to the map
275 | addHeader(key, value);
276 | }
277 |
278 | /**
279 | * Add header key-value std::pair to the map
280 | *
281 | * @param key String representation of the Header Key
282 | * @param value String representation of the Header value
283 | */
284 | void HTTPMessage::addHeader(std::string const& key, std::string const& value) {
285 | headers.try_emplace(key, value);
286 | }
287 |
288 | /**
289 | * Add header key-value std::pair to the map (Integer value)
290 | * Integer value is converted to a string
291 | *
292 | * @param key String representation of the Header Key
293 | * @param value Integer representation of the Header value
294 | */
295 | void HTTPMessage::addHeader(std::string const& key, int32_t value) {
296 | headers.try_emplace(key, std::format("{}", value));
297 | }
298 |
299 | /**
300 | * Get Header Value
301 | * Given a header name (key), return the value associated with it in the headers map
302 | *
303 | * @param key Key to identify the header
304 | */
305 | std::string HTTPMessage::getHeaderValue(std::string const& key) const {
306 |
307 | char c = 0;
308 | std::string key_lower = "";
309 |
310 | // Lookup in map
311 | auto it = headers.find(key);
312 |
313 | // Key wasn't found, try an all lowercase variant as some clients won't always use proper capitalization
314 | if (it == headers.end()) {
315 |
316 | for (uint32_t i = 0; i < key.length(); i++) {
317 | c = key.at(i);
318 | key_lower += tolower(c);
319 | }
320 |
321 | // Still not found, return empty string to indicate the Header value doesnt exist
322 | it = headers.find(key_lower);
323 | if (it == headers.end())
324 | return "";
325 | }
326 |
327 | // Otherwise, return the value
328 | return it->second;
329 | }
330 |
331 | /**
332 | * Get Header String
333 | * Get the full formatted header string "Header: value" from the headers map at position index
334 | *
335 | * @param index Position in the headers map to retrieve a formatted header string
336 | * @ret Formatted string with header name and value
337 | */
338 | std::string HTTPMessage::getHeaderStr(int32_t index) const {
339 | int32_t i = 0;
340 | std::string ret = "";
341 | for (auto const &[key, value] : headers) {
342 | if (i == index) {
343 | ret = std::format("{}: {}", key, value);
344 | break;
345 | }
346 |
347 | i++;
348 | }
349 | return ret;
350 | }
351 |
352 | /**
353 | * Get Number of Headers
354 | * Return the number of headers in the headers map
355 | *
356 | * @return size of the map
357 | */
358 | uint32_t HTTPMessage::getNumHeaders() const {
359 | return headers.size();
360 | }
361 |
362 | /**
363 | * Clear Headers
364 | * Removes all headers from the internal map
365 | */
366 | void HTTPMessage::clearHeaders() {
367 | headers.clear();
368 | }
369 |
370 |
--------------------------------------------------------------------------------
/src/HTTPMessage.h:
--------------------------------------------------------------------------------
1 | /**
2 | ByteBuffer
3 | HTTPMessage.h
4 | Copyright 2011-2025 Ramsey Kant
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 |
19 | #ifndef _HTTPMESSAGE_H_
20 | #define _HTTPMESSAGE_H_
21 |
22 | #include