├── .gitignore ├── LICENSE.md ├── README.md ├── fabfile.py ├── fts ├── __init__.py ├── mock_backend.py ├── test_can_handle_client_disconnect.py ├── test_can_handle_large_response.py ├── test_can_handle_multiple_simultaneous_connections.py ├── test_can_proxy_http_request_to_backend.py ├── test_gives_error_messages_on_nonexistent_or_buggy_config_files.py ├── test_gives_error_on_long_requests.py └── test_log_has_timestamp.py ├── handle_integration_error ├── promote_to_live ├── run_integration_tests ├── sampleconfig.lua ├── setup-env.sh └── src ├── Makefile ├── connection.c ├── connection.h ├── epollinterface.c ├── epollinterface.h ├── logging.c ├── logging.h ├── netutils.c ├── netutils.h ├── rsp.c ├── server_socket.c └── server_socket.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .*.swp 3 | rsp 4 | src/*.o 5 | src/rsp 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Giles Thomas 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Really Simple Proxy 2 | --------------------- 3 | 4 | Trying to write a fast HTTP load-balancing proxy using C just so I can understand the 5 | underpinnings of nginx and the like. 6 | 7 | See [this blog post](http://www.gilesthomas.com/?p=634) for details. 8 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | from fabric.api import run 2 | 3 | def deploy(): 4 | run("service rsp stop") 5 | run("cd /home/rsp/rsp/src && git pull origin master && make clean && make && make install") 6 | run("service rsp start") 7 | -------------------------------------------------------------------------------- /fts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpjt/rsp/9d1b40c69a05cc784b78c2386efa2afcce899b8c/fts/__init__.py -------------------------------------------------------------------------------- /fts/mock_backend.py: -------------------------------------------------------------------------------- 1 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 2 | from SocketServer import ThreadingMixIn 3 | from threading import Thread 4 | import time 5 | 6 | 7 | 8 | class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): 9 | pass 10 | 11 | 12 | 13 | def create_and_start_backend(request_speed, response=None): 14 | if response is None: 15 | response = "Hello from the mock server\n" 16 | 17 | 18 | class MockServerRequestHandler(BaseHTTPRequestHandler): 19 | def do_GET(self): 20 | time.sleep(request_speed) 21 | self.send_response(200) 22 | self.end_headers() 23 | self.wfile.write(response) 24 | 25 | httpd = ThreadedHTTPServer(('127.0.0.1', 8888), MockServerRequestHandler) 26 | server_thread = Thread(target=httpd.serve_forever) 27 | server_thread.daemon = True 28 | server_thread.start() 29 | return httpd 30 | 31 | -------------------------------------------------------------------------------- /fts/test_can_handle_client_disconnect.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pexpect 3 | import requests 4 | import socket 5 | from tempfile import mkstemp 6 | import unittest 7 | 8 | from mock_backend import create_and_start_backend 9 | 10 | 11 | RSP_BINARY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "rsp")) 12 | 13 | 14 | 15 | class TestCanHandleClientDisconnect(unittest.TestCase): 16 | 17 | def test_can_handle_client_disconnect(self): 18 | backend = create_and_start_backend(0, "0123456789\n" * 1024 * 1024) 19 | 20 | _, config_file = mkstemp(suffix=".lua") 21 | with open(config_file, "w") as f: 22 | f.writelines([ 23 | 'listenPort = "8000"' 24 | 'backendAddress = "127.0.0.1"' 25 | 'backendPort = "8888"' 26 | ]) 27 | server = pexpect.spawn(RSP_BINARY, [config_file]) 28 | server.expect("Started. Listening on port 8000.") 29 | 30 | try: 31 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) 32 | sock.connect(("localhost", 8000)) 33 | sock.send("GET / HTTP/1.1\r\n") 34 | sock.send("\r\n") 35 | sock.recv(1024) 36 | sock.close() 37 | 38 | # Check it didn't crash 39 | response = requests.get("http://127.0.0.1:8000", timeout=10) 40 | self.assertEqual(response.status_code, 200) 41 | finally: 42 | server.kill(9) 43 | print "-" * 80 44 | for line in server.readlines(): 45 | print line, 46 | print "-" * 80 47 | os.remove(config_file) 48 | 49 | backend.shutdown() 50 | -------------------------------------------------------------------------------- /fts/test_can_handle_large_response.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import pexpect 4 | from tempfile import mkstemp 5 | import unittest 6 | 7 | from mock_backend import create_and_start_backend 8 | 9 | 10 | RSP_BINARY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "rsp")) 11 | 12 | 13 | 14 | class TestCanHandleLargeResponse(unittest.TestCase): 15 | 16 | def test_large_response(self): 17 | # We start up a backend 18 | expected_response = "0123456789\n" * 1024 * 1024 19 | backend = create_and_start_backend(0, expected_response) 20 | 21 | # ...and an rsp option that proxies everything to it. 22 | _, config_file = mkstemp(suffix=".lua") 23 | with open(config_file, "w") as f: 24 | f.writelines([ 25 | 'listenPort = "8000"' 26 | 'backendAddress = "127.0.0.1"' 27 | 'backendPort = "8888"' 28 | ]) 29 | server = pexpect.spawn(RSP_BINARY, [config_file]) 30 | server.expect("Started. Listening on port 8000.") 31 | try: 32 | # Make a request and check it works. 33 | response = requests.get("http://127.0.0.1:8000") 34 | self.assertEqual(response.status_code, 200) 35 | self.assertEqual(len(response.text), len(expected_response)) 36 | self.assertEqual(response.text, expected_response, "Response incorrect") 37 | finally: 38 | server.kill(9) 39 | os.remove(config_file) 40 | 41 | backend.shutdown() 42 | -------------------------------------------------------------------------------- /fts/test_can_handle_multiple_simultaneous_connections.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pexpect 3 | import requests 4 | from tempfile import mkstemp 5 | from threading import Thread 6 | import time 7 | import unittest 8 | 9 | from mock_backend import create_and_start_backend 10 | 11 | RSP_BINARY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "rsp")) 12 | 13 | 14 | class TestCanHandleMultipleSimultaneousConnections(unittest.TestCase): 15 | 16 | def test_multiple_roughly_simultaneous_requests_complete_in_time_roughly_equivalent_to_one_request(self): 17 | backend = create_and_start_backend(5) 18 | 19 | _, config_file = mkstemp(suffix=".lua") 20 | with open(config_file, "w") as f: 21 | f.writelines([ 22 | 'listenPort = "8000"' 23 | 'backendAddress = "127.0.0.1"' 24 | 'backendPort = "8888"' 25 | ]) 26 | server = pexpect.spawn(RSP_BINARY, [config_file]) 27 | server.expect("Started. Listening on port 8000.") 28 | try: 29 | responses = [] 30 | errors = [] 31 | def make_request(): 32 | try: 33 | responses.append(requests.get("http://127.0.0.1:8000")) 34 | except Exception, e: 35 | errors.append(e) 36 | 37 | start = time.time() 38 | client_threads = [] 39 | for i in range(4): 40 | t = Thread(target=make_request) 41 | t.daemon = True 42 | t.start() 43 | client_threads.append(t) 44 | 45 | for client_thread in client_threads: 46 | client_thread.join() 47 | 48 | self.assertTrue(time.time() - start < 6) 49 | 50 | if errors != []: 51 | raise errors[0] 52 | 53 | for response in responses: 54 | self.assertEqual(response.status_code, 200) 55 | self.assertEqual(response.text, "Hello from the mock server\n") 56 | finally: 57 | server.kill(9) 58 | os.remove(config_file) 59 | 60 | backend.shutdown() 61 | -------------------------------------------------------------------------------- /fts/test_can_proxy_http_request_to_backend.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import pexpect 4 | from tempfile import mkstemp 5 | import unittest 6 | 7 | from mock_backend import create_and_start_backend 8 | 9 | 10 | RSP_BINARY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "rsp")) 11 | 12 | 13 | 14 | class TestCanProxyHTTPRequestToBackend(unittest.TestCase): 15 | 16 | def test_simple_proxying(self): 17 | # We start up a backend 18 | backend = create_and_start_backend(0) 19 | 20 | # ...and an rsp option that proxies everything to it. 21 | _, config_file = mkstemp(suffix=".lua") 22 | with open(config_file, "w") as f: 23 | f.writelines([ 24 | 'listenPort = "8000"' 25 | 'backendAddress = "127.0.0.1"' 26 | 'backendPort = "8888"' 27 | ]) 28 | server = pexpect.spawn(RSP_BINARY, [config_file]) 29 | server.expect("Started. Listening on port 8000.") 30 | try: 31 | # Make a request and check it works. 32 | response = requests.get("http://127.0.0.1:8000") 33 | self.assertEqual(response.status_code, 200) 34 | self.assertEqual(response.text, "Hello from the mock server\n") 35 | 36 | # Make a second request and check it works too. 37 | response = requests.get("http://127.0.0.1:8000") 38 | self.assertEqual(response.status_code, 200) 39 | self.assertEqual(response.text, "Hello from the mock server\n") 40 | finally: 41 | server.kill(9) 42 | os.remove(config_file) 43 | 44 | backend.shutdown() 45 | -------------------------------------------------------------------------------- /fts/test_gives_error_messages_on_nonexistent_or_buggy_config_files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pexpect 3 | from tempfile import mkstemp 4 | import unittest 5 | 6 | 7 | RSP_BINARY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "rsp")) 8 | 9 | 10 | 11 | class TestGivesAppropriateErrorMessagesOnNonExistentOrBuggyConfigFiles(unittest.TestCase): 12 | 13 | def test_gives_appropriate_error_message_on_nonexistent_config_file(self): 14 | server = pexpect.spawn(RSP_BINARY, ["/definitely_does_not_exist"]) 15 | server.expect("Error parsing config file: cannot open /definitely_does_not_exist: No such file or directory") 16 | 17 | 18 | def test_gives_appropriate_error_message_on_buggy_config_file(self): 19 | _, config_file = mkstemp(suffix=".lua") 20 | with open(config_file, "w") as f: 21 | f.write("dfs'df';f'#as'\n") 22 | server = pexpect.spawn(RSP_BINARY, [config_file]) 23 | server.expect("Error parsing config file:") 24 | 25 | -------------------------------------------------------------------------------- /fts/test_gives_error_on_long_requests.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pexpect 3 | import socket 4 | from tempfile import mkstemp 5 | import unittest 6 | 7 | from mock_backend import create_and_start_backend 8 | 9 | 10 | RSP_BINARY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "rsp")) 11 | 12 | 13 | 14 | class TestGivesErrorOnLongRequests(unittest.TestCase): 15 | 16 | def test_gives_error_on_long_requests(self): 17 | backend = create_and_start_backend(5) 18 | 19 | _, config_file = mkstemp(suffix=".lua") 20 | with open(config_file, "w") as f: 21 | f.writelines([ 22 | 'listenPort = "8000"' 23 | 'backendAddress = "127.0.0.1"' 24 | 'backendPort = "8888"' 25 | ]) 26 | server = pexpect.spawn(RSP_BINARY, [config_file]) 27 | server.expect("Started. Listening on port 8000.") 28 | 29 | try: 30 | sock = socket.socket() 31 | sock.connect(("localhost", 8000)) 32 | sock.send("GET / HTTP/1.1\r") 33 | while True: 34 | try: 35 | sent = sock.send("A-Header: some values\r") 36 | except: 37 | break 38 | if sent == 0: 39 | break 40 | return_value = sock.recv(10000) 41 | self.assertTrue(return_value.startswith("HTTP/1.0 414 ")) 42 | finally: 43 | server.kill(9) 44 | os.remove(config_file) 45 | 46 | backend.shutdown() 47 | -------------------------------------------------------------------------------- /fts/test_log_has_timestamp.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import os 3 | import pexpect 4 | from tempfile import mkstemp 5 | import unittest 6 | 7 | from mock_backend import create_and_start_backend 8 | 9 | 10 | RSP_BINARY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "rsp")) 11 | 12 | 13 | 14 | class TestLogHasTimestamp(unittest.TestCase): 15 | 16 | def test_simple_proxying(self): 17 | # We start up a backend 18 | backend = create_and_start_backend(0) 19 | 20 | # ...and an rsp option that proxies everything to it. 21 | _, config_file = mkstemp(suffix=".lua") 22 | with open(config_file, "w") as f: 23 | f.writelines([ 24 | 'listenPort = "8000"' 25 | 'backendAddress = "127.0.0.1"' 26 | 'backendPort = "8888"' 27 | ]) 28 | server = pexpect.spawn(RSP_BINARY, [config_file]) 29 | try: 30 | now = datetime.now() 31 | expected_timestamp = now.strftime("%Y-%m-%d %H:%M") 32 | server.expect("\\[%s:.*\\] Started. Listening on port 8000." % (expected_timestamp,), timeout=5) 33 | finally: 34 | server.kill(9) 35 | os.remove(config_file) 36 | 37 | backend.shutdown() 38 | -------------------------------------------------------------------------------- /handle_integration_error: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | -------------------------------------------------------------------------------- /promote_to_live: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo About to promote to live. 3 | 4 | # Stash away the directory with the source in it 5 | SOURCE_DIR=`pwd` 6 | 7 | # Push from the integration repo to GitHub 8 | cd $1 9 | git push origin master 10 | 11 | # Back to the source 12 | cd $SOURCE_DIR 13 | 14 | # Push to gilesthomas.com 15 | fab -H root@www.gilesthomas.com deploy 16 | 17 | echo Promoted, all done. 18 | -------------------------------------------------------------------------------- /run_integration_tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ $# -eq 0 ] 3 | then 4 | ARGS=discover 5 | else 6 | ARGS=$1 7 | fi 8 | 9 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 10 | cd $DIR/src 11 | make & make install 12 | cd $DIR/fts 13 | python -m unittest $ARGS 14 | -------------------------------------------------------------------------------- /sampleconfig.lua: -------------------------------------------------------------------------------- 1 | listenPort = "8000" 2 | backendAddress = "127.0.0.1" 3 | backendPort = "80" 4 | -------------------------------------------------------------------------------- /setup-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo apt-get update 3 | 4 | sudo apt-get install -y vim build-essential git 5 | 6 | sudo apt-get install python-dev 7 | sudo apt-get install python-pip 8 | 9 | sudo pip install fabric 10 | sudo pip install pexpect 11 | sudo pip install pyflakes 12 | sudo pip install requests 13 | 14 | git clone http://luajit.org/git/luajit-2.0.git 15 | cd luajit-2.0 16 | make && sudo make install 17 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | CC := gcc 2 | C_FILES := $(wildcard *.c) 3 | HEADER_FILES := $(wildcard *.h) 4 | OBJ_FILES := $(patsubst %.c,%.o,$(C_FILES)) 5 | INCLUDE_DIRS := -I/usr/local/include/luajit-2.0/ 6 | LDFLAGS := -Wl,-rpath,/usr/local/lib 7 | LIBS := -lluajit-5.1 8 | 9 | all: rsp 10 | 11 | install: rsp 12 | cp rsp .. 13 | 14 | clean: 15 | rm -f $(OBJ_FILES) 16 | rm -f rsp 17 | 18 | %.o: %.c $(HEADER_FILES) 19 | $(CC) -c -Wall -g -std=gnu99 $(INCLUDE_DIRS) $< 20 | 21 | rsp: $(OBJ_FILES) 22 | $(CC) -o $@ $(OBJ_FILES) $(LDFLAGS) $(LIBS) 23 | -------------------------------------------------------------------------------- /src/connection.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | #include "epollinterface.h" 12 | #include "connection.h" 13 | #include "logging.h" 14 | #include "netutils.h" 15 | 16 | 17 | #define BUFFER_SIZE 4096 18 | 19 | struct data_buffer_entry { 20 | int is_close_message; 21 | char* data; 22 | int current_offset; 23 | int len; 24 | struct data_buffer_entry* next; 25 | }; 26 | 27 | 28 | void connection_really_close(struct epoll_event_handler* self) 29 | { 30 | struct connection_closure* closure = (struct connection_closure* ) self->closure; 31 | struct data_buffer_entry* next; 32 | while (closure->write_buffer != NULL) { 33 | next = closure->write_buffer->next; 34 | if (!closure->write_buffer->is_close_message) { 35 | epoll_add_to_free_list(closure->write_buffer->data); 36 | } 37 | epoll_add_to_free_list(closure->write_buffer); 38 | closure->write_buffer = next; 39 | } 40 | 41 | epoll_remove_handler(self); 42 | close(self->fd); 43 | epoll_add_to_free_list(self->closure); 44 | epoll_add_to_free_list(self); 45 | rsp_log("Freed connection %p", self); 46 | } 47 | 48 | 49 | void connection_on_close_event(struct epoll_event_handler* self) 50 | { 51 | struct connection_closure* closure = (struct connection_closure*) self->closure; 52 | if (closure->on_close != NULL) { 53 | closure->on_close(closure->on_close_closure); 54 | } 55 | connection_close(self); 56 | } 57 | 58 | 59 | void connection_on_out_event(struct epoll_event_handler* self) 60 | { 61 | struct connection_closure* closure = (struct connection_closure*) self->closure; 62 | int written; 63 | int to_write; 64 | struct data_buffer_entry* temp; 65 | while (closure->write_buffer != NULL) { 66 | if (closure->write_buffer->is_close_message) { 67 | connection_really_close(self); 68 | return; 69 | } 70 | 71 | to_write = closure->write_buffer->len - closure->write_buffer->current_offset; 72 | written = write(self->fd, closure->write_buffer->data + closure->write_buffer->current_offset, to_write); 73 | if (written != to_write) { 74 | if (written == -1) { 75 | if (errno == ECONNRESET || errno == EPIPE) { 76 | rsp_log_error("On out event write error"); 77 | connection_on_close_event(self); 78 | return; 79 | } 80 | if (errno != EAGAIN && errno != EWOULDBLOCK) { 81 | rsp_log_error("Error writing to client"); 82 | exit(-1); 83 | } 84 | written = 0; 85 | } 86 | closure->write_buffer->current_offset += written; 87 | break; 88 | } else { 89 | temp = closure->write_buffer; 90 | closure->write_buffer = closure->write_buffer->next; 91 | epoll_add_to_free_list(temp->data); 92 | epoll_add_to_free_list(temp); 93 | } 94 | } 95 | } 96 | 97 | 98 | void connection_on_in_event(struct epoll_event_handler* self) 99 | { 100 | struct connection_closure* closure = (struct connection_closure*) self->closure; 101 | char read_buffer[BUFFER_SIZE]; 102 | int bytes_read; 103 | 104 | while ((bytes_read = read(self->fd, read_buffer, BUFFER_SIZE)) != -1 && bytes_read != 0) { 105 | if (bytes_read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { 106 | return; 107 | } 108 | 109 | if (bytes_read == 0 || bytes_read == -1) { 110 | connection_on_close_event(self); 111 | return; 112 | } 113 | 114 | if (closure->on_read != NULL) { 115 | closure->on_read(closure->on_read_closure, read_buffer, bytes_read); 116 | } 117 | } 118 | } 119 | 120 | 121 | void connection_handle_event(struct epoll_event_handler* self, uint32_t events) 122 | { 123 | if (events & EPOLLOUT) { 124 | connection_on_out_event(self); 125 | } 126 | 127 | if (events & EPOLLIN) { 128 | connection_on_in_event(self); 129 | } 130 | 131 | if ((events & EPOLLERR) | (events & EPOLLHUP) | (events & EPOLLRDHUP)) { 132 | connection_on_close_event(self); 133 | } 134 | 135 | } 136 | 137 | 138 | void add_write_buffer_entry(struct connection_closure* closure, struct data_buffer_entry* new_entry) 139 | { 140 | struct data_buffer_entry* last_buffer_entry; 141 | if (closure->write_buffer == NULL) { 142 | closure->write_buffer = new_entry; 143 | } else { 144 | for (last_buffer_entry=closure->write_buffer; last_buffer_entry->next != NULL; last_buffer_entry=last_buffer_entry->next) 145 | ; 146 | last_buffer_entry->next = new_entry; 147 | } 148 | } 149 | 150 | 151 | void connection_write(struct epoll_event_handler* self, char* data, int len) 152 | { 153 | struct connection_closure* closure = (struct connection_closure* ) self->closure; 154 | 155 | int written = 0; 156 | if (closure->write_buffer == NULL) { 157 | written = write(self->fd, data, len); 158 | if (written == len) { 159 | return; 160 | } 161 | } 162 | if (written == -1) { 163 | if (errno == ECONNRESET || errno == EPIPE) { 164 | rsp_log_error("Connection write error"); 165 | connection_on_close_event(self); 166 | return; 167 | } 168 | if (errno != EAGAIN && errno != EWOULDBLOCK) { 169 | rsp_log_error("Error writing to client"); 170 | exit(-1); 171 | } 172 | written = 0; 173 | } 174 | 175 | int unwritten = len - written; 176 | struct data_buffer_entry* new_entry = malloc(sizeof(struct data_buffer_entry)); 177 | new_entry->is_close_message = 0; 178 | new_entry->data = malloc(unwritten); 179 | memcpy(new_entry->data, data + written, unwritten); 180 | new_entry->current_offset = 0; 181 | new_entry->len = unwritten; 182 | new_entry->next = NULL; 183 | 184 | add_write_buffer_entry(closure, new_entry); 185 | } 186 | 187 | 188 | void connection_close(struct epoll_event_handler* self) 189 | { 190 | struct connection_closure* closure = (struct connection_closure* ) self->closure; 191 | closure->on_read = NULL; 192 | closure->on_close = NULL; 193 | if (closure->write_buffer == NULL) { 194 | connection_really_close(self); 195 | } else { 196 | struct data_buffer_entry* new_entry = malloc(sizeof(struct data_buffer_entry)); 197 | new_entry->is_close_message = 1; 198 | new_entry->next = NULL; 199 | 200 | add_write_buffer_entry(closure, new_entry); 201 | } 202 | } 203 | 204 | 205 | struct epoll_event_handler* create_connection(int client_socket_fd) 206 | { 207 | make_socket_non_blocking(client_socket_fd); 208 | 209 | struct connection_closure* closure = malloc(sizeof(struct connection_closure)); 210 | closure->write_buffer = NULL; 211 | 212 | struct epoll_event_handler* result = malloc(sizeof(struct epoll_event_handler)); 213 | rsp_log("Created connection epoll handler %p", result); 214 | result->fd = client_socket_fd; 215 | result->handle = connection_handle_event; 216 | result->closure = closure; 217 | 218 | 219 | epoll_add_handler(result, EPOLLIN | EPOLLRDHUP | EPOLLET | EPOLLOUT); 220 | 221 | return result; 222 | } 223 | -------------------------------------------------------------------------------- /src/connection.h: -------------------------------------------------------------------------------- 1 | struct connection_closure { 2 | void (*on_read)(void* closure, char* buffer, int len); 3 | void* on_read_closure; 4 | 5 | void (*on_close)(void* closure); 6 | void* on_close_closure; 7 | 8 | struct data_buffer_entry* write_buffer; 9 | }; 10 | 11 | extern void connection_write(struct epoll_event_handler* self, char* data, int len); 12 | 13 | extern void connection_close(struct epoll_event_handler* self); 14 | 15 | extern struct epoll_event_handler* create_connection(int connection_fd); 16 | -------------------------------------------------------------------------------- /src/epollinterface.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "epollinterface.h" 8 | #include "logging.h" 9 | 10 | 11 | int epoll_fd; 12 | 13 | 14 | void epoll_init() 15 | { 16 | epoll_fd = epoll_create1(0); 17 | if (epoll_fd == -1) { 18 | rsp_log_error("Couldn't create epoll FD"); 19 | exit(1); 20 | } 21 | } 22 | 23 | 24 | void epoll_add_handler(struct epoll_event_handler* handler, uint32_t event_mask) 25 | { 26 | struct epoll_event event; 27 | 28 | memset(&event, 0, sizeof(struct epoll_event)); 29 | event.data.ptr = handler; 30 | event.events = event_mask; 31 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, handler->fd, &event) == -1) { 32 | rsp_log_error("Couldn't register server socket with epoll"); 33 | exit(-1); 34 | } 35 | } 36 | 37 | 38 | void epoll_remove_handler(struct epoll_event_handler* handler) 39 | { 40 | epoll_ctl(epoll_fd, EPOLL_CTL_DEL, handler->fd, NULL); 41 | } 42 | 43 | 44 | struct free_list_entry { 45 | void* block; 46 | struct free_list_entry* next; 47 | }; 48 | 49 | struct free_list_entry* free_list = NULL; 50 | 51 | 52 | void epoll_add_to_free_list(void* block) 53 | { 54 | struct free_list_entry* entry = malloc(sizeof(struct free_list_entry)); 55 | entry->block = block; 56 | entry->next = free_list; 57 | free_list = entry; 58 | } 59 | 60 | 61 | void epoll_do_reactor_loop() 62 | { 63 | struct epoll_event current_epoll_event; 64 | 65 | while (1) { 66 | struct epoll_event_handler* handler; 67 | 68 | epoll_wait(epoll_fd, ¤t_epoll_event, 1, -1); 69 | handler = (struct epoll_event_handler*) current_epoll_event.data.ptr; 70 | handler->handle(handler, current_epoll_event.events); 71 | 72 | struct free_list_entry* temp; 73 | while (free_list != NULL) { 74 | free(free_list->block); 75 | temp = free_list->next; 76 | free(free_list); 77 | free_list = temp; 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/epollinterface.h: -------------------------------------------------------------------------------- 1 | struct epoll_event_handler { 2 | int fd; 3 | void (*handle)(struct epoll_event_handler*, uint32_t); 4 | void* closure; 5 | }; 6 | 7 | extern void epoll_init(); 8 | 9 | extern void epoll_add_handler(struct epoll_event_handler* handler, uint32_t event_mask); 10 | 11 | extern void epoll_remove_handler(struct epoll_event_handler* handler); 12 | 13 | extern void epoll_add_to_free_list(void* block); 14 | 15 | extern void epoll_do_reactor_loop(); 16 | -------------------------------------------------------------------------------- /src/logging.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | void rsp_log(char* format, ...) 11 | { 12 | char without_ms[64]; 13 | char with_ms[64]; 14 | struct timeval tv; 15 | struct tm *tm; 16 | 17 | gettimeofday(&tv, NULL); 18 | if ((tm = localtime(&tv.tv_sec)) != NULL) 19 | { 20 | strftime(without_ms, sizeof(without_ms), "%Y-%m-%d %H:%M:%S.%%06u %z", tm); 21 | snprintf(with_ms, sizeof(with_ms), without_ms, tv.tv_usec); 22 | fprintf(stdout, "[%s] ", with_ms); 23 | } 24 | 25 | va_list argptr; 26 | va_start(argptr, format); 27 | vfprintf(stdout, format, argptr); 28 | va_end(argptr); 29 | 30 | fprintf(stdout, "\n"); 31 | 32 | fflush(stdout); 33 | } 34 | 35 | 36 | void rsp_log_error(char* message) 37 | { 38 | char* error = strerror(errno); 39 | rsp_log("%s: %s", message, error); 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/logging.h: -------------------------------------------------------------------------------- 1 | extern void rsp_log(char* format, ...); 2 | 3 | extern void rsp_log_error(char* message); 4 | -------------------------------------------------------------------------------- /src/netutils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | #include "logging.h" 11 | #include "netutils.h" 12 | 13 | 14 | void make_socket_non_blocking(int socket_fd) 15 | { 16 | int flags; 17 | 18 | flags = fcntl(socket_fd, F_GETFL, 0); 19 | if (flags == -1) { 20 | rsp_log_error("Couldn't get socket flags"); 21 | exit(1); 22 | } 23 | 24 | flags |= O_NONBLOCK; 25 | if (fcntl(socket_fd, F_SETFL, flags) == -1) { 26 | rsp_log_error("Couldn't set socket flags"); 27 | exit(-1); 28 | } 29 | } 30 | 31 | 32 | int connect_to_backend(char* backend_host, 33 | char* backend_port_str) 34 | { 35 | struct addrinfo hints; 36 | memset(&hints, 0, sizeof(struct addrinfo)); 37 | hints.ai_family = AF_UNSPEC; 38 | hints.ai_socktype = SOCK_STREAM; 39 | 40 | int getaddrinfo_error; 41 | struct addrinfo* addrs; 42 | getaddrinfo_error = getaddrinfo(backend_host, backend_port_str, &hints, &addrs); 43 | if (getaddrinfo_error != 0) { 44 | if (getaddrinfo_error == EAI_SYSTEM) { 45 | rsp_log_error("Couldn't find backend"); 46 | } else { 47 | rsp_log("Couldn't find backend: %s", gai_strerror(getaddrinfo_error)); 48 | } 49 | exit(1); 50 | } 51 | 52 | int backend_socket_fd; 53 | struct addrinfo* addrs_iter; 54 | for (addrs_iter = addrs; 55 | addrs_iter != NULL; 56 | addrs_iter = addrs_iter->ai_next) 57 | { 58 | backend_socket_fd = socket(addrs_iter->ai_family, 59 | addrs_iter->ai_socktype, 60 | addrs_iter->ai_protocol); 61 | if (backend_socket_fd == -1) { 62 | continue; 63 | } 64 | 65 | if (connect(backend_socket_fd, 66 | addrs_iter->ai_addr, 67 | addrs_iter->ai_addrlen) != -1) { 68 | break; 69 | } 70 | 71 | close(backend_socket_fd); 72 | } 73 | 74 | if (addrs_iter == NULL) { 75 | rsp_log("Couldn't connect to backend"); 76 | exit(1); 77 | } 78 | 79 | freeaddrinfo(addrs); 80 | 81 | return backend_socket_fd; 82 | } 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/netutils.h: -------------------------------------------------------------------------------- 1 | extern void make_socket_non_blocking(int socket_fd); 2 | 3 | extern int connect_to_backend(char* backend_host, char* backend_port_str); 4 | -------------------------------------------------------------------------------- /src/rsp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "epollinterface.h" 10 | #include "logging.h" 11 | #include "server_socket.h" 12 | 13 | 14 | char* get_config_opt(lua_State* L, char* name) { 15 | lua_getglobal(L, name); 16 | if (!lua_isstring(L, -1)) { 17 | fprintf(stderr, "%s must be a string", name); 18 | exit(1); 19 | } 20 | return (char*) lua_tostring(L, -1); 21 | } 22 | 23 | 24 | int main(int argc, char* argv[]) 25 | { 26 | if (argc != 2) { 27 | fprintf(stderr, 28 | "Usage: %s \n", 29 | argv[0]); 30 | exit(1); 31 | } 32 | 33 | lua_State *L = lua_open(); 34 | if (luaL_dofile(L, argv[1]) != 0) { 35 | fprintf(stderr, "Error parsing config file: %s\n", lua_tostring(L, -1)); 36 | exit(1); 37 | } 38 | char* server_port_str = get_config_opt(L, "listenPort"); 39 | char* backend_addr = get_config_opt(L, "backendAddress"); 40 | char* backend_port_str = get_config_opt(L, "backendPort"); 41 | 42 | signal(SIGPIPE, SIG_IGN); 43 | 44 | epoll_init(); 45 | 46 | create_server_socket_handler(server_port_str, 47 | backend_addr, 48 | backend_port_str); 49 | 50 | rsp_log("Started. Listening on port %s.", server_port_str); 51 | epoll_do_reactor_loop(); 52 | 53 | return 0; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/server_socket.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "epollinterface.h" 10 | #include "connection.h" 11 | #include "logging.h" 12 | #include "netutils.h" 13 | #include "server_socket.h" 14 | 15 | #define MAX_LISTEN_BACKLOG 4096 16 | 17 | 18 | struct server_socket_event_data { 19 | char* backend_addr; 20 | char* backend_port_str; 21 | }; 22 | 23 | 24 | struct proxy_data { 25 | struct epoll_event_handler* client; 26 | struct epoll_event_handler* backend; 27 | }; 28 | 29 | 30 | void on_client_read(void* closure, char* buffer, int len) 31 | { 32 | struct proxy_data* data = (struct proxy_data*) closure; 33 | if (data->backend == NULL) { 34 | return; 35 | } 36 | connection_write(data->backend, buffer, len); 37 | } 38 | 39 | 40 | void on_client_close(void* closure) 41 | { 42 | struct proxy_data* data = (struct proxy_data*) closure; 43 | if (data->backend == NULL) { 44 | return; 45 | } 46 | connection_close(data->backend); 47 | data->client = NULL; 48 | data->backend = NULL; 49 | epoll_add_to_free_list(closure); 50 | } 51 | 52 | 53 | void on_backend_read(void* closure, char* buffer, int len) 54 | { 55 | struct proxy_data* data = (struct proxy_data*) closure; 56 | if (data->client == NULL) { 57 | return; 58 | } 59 | connection_write(data->client, buffer, len); 60 | } 61 | 62 | 63 | void on_backend_close(void* closure) 64 | { 65 | struct proxy_data* data = (struct proxy_data*) closure; 66 | if (data->client == NULL) { 67 | return; 68 | } 69 | connection_close(data->client); 70 | data->client = NULL; 71 | data->backend = NULL; 72 | epoll_add_to_free_list(closure); 73 | } 74 | 75 | 76 | void handle_client_connection(int client_socket_fd, 77 | char* backend_host, 78 | char* backend_port_str) 79 | { 80 | struct epoll_event_handler* client_connection; 81 | rsp_log("Creating connection object for incoming connection..."); 82 | client_connection = create_connection(client_socket_fd); 83 | 84 | int backend_socket_fd = connect_to_backend(backend_host, backend_port_str); 85 | struct epoll_event_handler* backend_connection; 86 | rsp_log("Creating connection object for backend connection..."); 87 | backend_connection = create_connection(backend_socket_fd); 88 | 89 | struct proxy_data* proxy = malloc(sizeof(struct proxy_data)); 90 | proxy->client = client_connection; 91 | proxy->backend = backend_connection; 92 | 93 | struct connection_closure* client_closure = (struct connection_closure*) client_connection->closure; 94 | client_closure->on_read = on_client_read; 95 | client_closure->on_read_closure = proxy; 96 | client_closure->on_close = on_client_close; 97 | client_closure->on_close_closure = proxy; 98 | 99 | struct connection_closure* backend_closure = (struct connection_closure*) backend_connection->closure; 100 | backend_closure->on_read = on_backend_read; 101 | backend_closure->on_read_closure = proxy; 102 | backend_closure->on_close = on_backend_close; 103 | backend_closure->on_close_closure = proxy; 104 | } 105 | 106 | 107 | 108 | void handle_server_socket_event(struct epoll_event_handler* self, uint32_t events) 109 | { 110 | struct server_socket_event_data* closure = (struct server_socket_event_data*) self->closure; 111 | 112 | int client_socket_fd; 113 | while (1) { 114 | client_socket_fd = accept(self->fd, NULL, NULL); 115 | if (client_socket_fd == -1) { 116 | if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { 117 | break; 118 | } else { 119 | rsp_log_error("Could not accept"); 120 | exit(1); 121 | } 122 | } 123 | 124 | handle_client_connection(client_socket_fd, 125 | closure->backend_addr, 126 | closure->backend_port_str); 127 | } 128 | } 129 | 130 | 131 | int create_and_bind(char* server_port_str) 132 | { 133 | struct addrinfo hints; 134 | memset(&hints, 0, sizeof(struct addrinfo)); 135 | hints.ai_family = AF_UNSPEC; 136 | hints.ai_socktype = SOCK_STREAM; 137 | hints.ai_flags = AI_PASSIVE; 138 | 139 | struct addrinfo* addrs; 140 | int getaddrinfo_error; 141 | getaddrinfo_error = getaddrinfo(NULL, server_port_str, &hints, &addrs); 142 | if (getaddrinfo_error != 0) { 143 | rsp_log("Couldn't find local host details: %s", gai_strerror(getaddrinfo_error)); 144 | exit(1); 145 | } 146 | 147 | int server_socket_fd; 148 | struct addrinfo* addr_iter; 149 | for (addr_iter = addrs; addr_iter != NULL; addr_iter = addr_iter->ai_next) { 150 | server_socket_fd = socket(addr_iter->ai_family, 151 | addr_iter->ai_socktype, 152 | addr_iter->ai_protocol); 153 | if (server_socket_fd == -1) { 154 | continue; 155 | } 156 | 157 | int so_reuseaddr = 1; 158 | if (setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &so_reuseaddr, sizeof(so_reuseaddr)) != 0) { 159 | continue; 160 | } 161 | 162 | if (bind(server_socket_fd, 163 | addr_iter->ai_addr, 164 | addr_iter->ai_addrlen) == 0) 165 | { 166 | break; 167 | } 168 | 169 | close(server_socket_fd); 170 | } 171 | 172 | if (addr_iter == NULL) { 173 | rsp_log("Couldn't bind"); 174 | exit(1); 175 | } 176 | 177 | freeaddrinfo(addrs); 178 | 179 | return server_socket_fd; 180 | } 181 | 182 | 183 | struct epoll_event_handler* create_server_socket_handler(char* server_port_str, 184 | char* backend_addr, 185 | char* backend_port_str) 186 | { 187 | 188 | int server_socket_fd; 189 | server_socket_fd = create_and_bind(server_port_str); 190 | make_socket_non_blocking(server_socket_fd); 191 | 192 | listen(server_socket_fd, MAX_LISTEN_BACKLOG); 193 | 194 | struct server_socket_event_data* closure = malloc(sizeof(struct server_socket_event_data)); 195 | closure->backend_addr = backend_addr; 196 | closure->backend_port_str = backend_port_str; 197 | 198 | struct epoll_event_handler* result = malloc(sizeof(struct epoll_event_handler)); 199 | result->fd = server_socket_fd; 200 | result->handle = handle_server_socket_event; 201 | result->closure = closure; 202 | 203 | epoll_add_handler(result, EPOLLIN | EPOLLET); 204 | 205 | return result; 206 | } 207 | 208 | 209 | -------------------------------------------------------------------------------- /src/server_socket.h: -------------------------------------------------------------------------------- 1 | extern struct epoll_event_handler* create_server_socket_handler(char* server_port_str, 2 | char* backend_addr, 3 | char* backend_port_str); 4 | --------------------------------------------------------------------------------