Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit c94cd5bcaf35212a70fc87a01215c8f5a952e867 @eklitzke committed May 13, 2012
Showing with 953 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +13 −0 LICENSE
  3. +30 −0 Makefile
  4. +19 −0 README.md
  5. +23 −0 configure
  6. +39 −0 example-server.cc
  7. +48 −0 garfield.gyp
  8. +128 −0 http_bench.cc
  9. +93 −0 src/connection.cc
  10. +52 −0 src/connection.h
  11. +9 −0 src/garfield.h
  12. +51 −0 src/headers.cc
  13. +38 −0 src/headers.h
  14. +8 −0 src/request.cc
  15. +41 −0 src/request.h
  16. +145 −0 src/response.cc
  17. +31 −0 src/response.h
  18. +115 −0 src/server.cc
  19. +66 −0 src/server.h
@@ -0,0 +1,4 @@
+*~
+build/
+example-server
+http_bench
13 LICENSE
@@ -0,0 +1,13 @@
+ Copyright 2012, Evan Klitzke <evan@eklitzke.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
@@ -0,0 +1,30 @@
+CC := g++
+CFLAGS := -Wall -pedantic -Os
+SERVER_SRCFILES := $(shell git ls-files src/) example-server.cc
+OUTDIR := build/out/Default
+SERVER_TARGET := example-server
+BENCH_TARGET := Default/http_bench
+
+all: $(SERVER_TARGET)
+
+clean:
+ rm -rf build/out/ build/src/
+ rm -f $(SERVER_TARGET)
+ rm -f $(BENCH_TARGET)
+
+build:
+ ./configure
+
+$(OUTDIR)/$(SERVER_TARGET): $(SERVER_SRCFILES) build
+ make -C build $(SERVER_TARGET)
+
+$(SERVER_TARGET): $(OUTDIR)/$(SERVER_TARGET)
+ @if [ ! -e "$@" ]; then echo -n "Creating ./$@ symlink..."; ln -sf $< $@; echo " done!"; fi
+
+$(OUTDIR)/$(BENCH_TARGET): src/http_bench.cc build
+ make -C build http_bench
+
+http_bench: $(BENCH_TARGET)
+ @if [ ! -e "$@" ]; then echo -n "Creating ./$@ symlink..."; ln -sf $< $@; echo " done!"; fi
+
+.PHONY: all clean
@@ -0,0 +1,19 @@
+Garfield is a toy HTTP server, written in C++ using boost::asio. It's
+single-threaded, but fully asynchronous (i.e. non-blocking), so it can handle
+many concurrent requests. It should be pretty fast, too.
+
+HTTP servers are *srs bidnis*, and writing a real one that handles all of the
+weird parts of the HTTP protocol takes a lot of work. You should consider
+Garfield to be "just for fun". You might find the source code useful if you're
+learning about boost::asio.
+
+Usage
+=====
+
+When you build Garfield, it will actually build a shared library
+(e.g. `libgarfield.so` on Linux systems). It's up to you to write a program that
+links against Garfield. There is no stand-alone server program.
+
+There's a sample program included, `example-server.cc`, that illustrates how to
+write a simple program that uses Garfield. The included Makefile with this
+project will build the example server, so you can run it and test it out.
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# A fake "configure" script to invoke gyp properly to build the Makefile
+
+set -u
+
+function invoke_gyp {
+ gyp --toplevel-dir=. --depth=src/ --generator-output=build garfield.gyp 2>/dev/null
+}
+
+rm -rf build
+
+# due to a bug in the gyp on F17, we might need to run this twice
+invoke_gyp
+if [ $? -ne 0 ]; then
+ invoke_gyp
+ if [ $? -ne 0 ]; then
+ echo "Failed to create Makefile!"
+ exit 1
+ fi
+fi
+
+echo "Created Makefile; type \"make\" to proceed."
@@ -0,0 +1,39 @@
+// Copyright 2012, Evan Klitzke <evan@eklitzke.org>
+
+#include <iostream>
+#include <boost/asio.hpp>
+#include <boost/program_options.hpp>
+
+#include "./src/garfield.h"
+
+namespace po = boost::program_options;
+
+void HelloWorldHandler(garfield::Request *req, garfield::Response *resp) {
+ resp->headers()->SetHeader("Content-Type", "text/plain");
+ resp->Write("Hello, world!\n");
+}
+
+int main(int argc, char **argv) {
+ po::options_description desc("Allowed options");
+ desc.add_options()
+ ("help", "produce help message")
+ ("port,p", po::value<int>()->default_value(8000),
+ "the port to bind on")
+ ;
+
+ po::variables_map vm;
+ po::store(po::parse_command_line(argc, argv, desc), vm);
+ po::notify(vm);
+
+ if (vm.count("help")) {
+ std::cout << desc;
+ return 1;
+ }
+
+ boost::asio::io_service ios;
+ garfield::HTTPServer server(&ios);
+ server.Bind(vm["port"].as<int>());
+ server.AddRoute("/", HelloWorldHandler);
+ ios.run(); // start the main loop
+ return 0;
+}
@@ -0,0 +1,48 @@
+# -*- python -*-
+{
+ 'target_defaults': {
+ 'cflags': ['-pedantic', '-Wall', '-std=c++11'],
+ 'libraries': [
+ '-lboost_system',
+ '-pthread',
+ ],
+ },
+ 'targets': [
+ {
+ 'type': 'shared_library',
+ 'cflags': ['-fPIC'],
+ 'target_name': 'libgarfield',
+ 'sources': [
+ 'src/connection.cc',
+ 'src/headers.cc',
+ 'src/request.cc',
+ 'src/response.cc',
+ 'src/server.cc',
+ ],
+ },
+ {
+ 'target_name': 'example-server',
+ 'type': 'executable',
+ 'cflags': ['-g'],
+ 'libraries': [
+ '-lboost_program_options',
+ '-lboost_regex',
+ ],
+ 'dependencies': ['libgarfield'],
+ 'sources': [
+ 'example-server.cc',
+ ],
+ },
+ {
+ 'target_name': 'http_bench',
+ 'type': 'executable',
+ 'libraries': [
+ '-lboost_program_options'
+ ],
+ 'sources': [
+ 'http_bench.cc',
+ ],
+ },
+
+ ],
+}
@@ -0,0 +1,128 @@
+// Copyright 2012, Evan Klitzke <evan@eklitzke.org>
+
+#include <boost/asio.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/program_options.hpp>
+
+#include <cassert>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace po = boost::program_options;
+
+class RequestCounter {
+ public:
+ explicit RequestCounter(int count) :counter_(count) {}
+ bool GetWork() { if (counter_) { counter_--; return true; } return false; }
+ private:
+ int counter_;
+};
+
+class HTTPClient {
+ public:
+ HTTPClient(boost::asio::io_service *io,
+ std::string host, int port, std::string path,
+ RequestCounter *counter)
+ :port_(port), path_(path), counter_(counter), sock_(*io) {}
+ void Run(boost::asio::ip::tcp::resolver::iterator endpoint);
+ private:
+ std::string host_;
+ int port_;
+ std::string path_;
+ RequestCounter *counter_;
+ boost::asio::ip::tcp::socket sock_;
+ std::string request_;
+
+ void OnConnect(const boost::system::error_code &error,
+ boost::asio::ip::tcp::resolver::iterator endpoint_iterator);
+ void TryGet();
+ void OnWrite(const boost::system::error_code &error,
+ std::size_t bytes_transferred);
+};
+
+void HTTPClient::Run(boost::asio::ip::tcp::resolver::iterator endpoint_iterator) {
+ request_ = "GET ";
+ request_ += path_;
+ request_ += " HTTP/1.1\r\nHost: localhost\r\n\r\n";
+ boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
+ boost::asio::async_connect(sock_, endpoint,
+ std::bind(&HTTPClient::OnConnect, this,
+ std::placeholders::_1,
+ ++endpoint_iterator));
+}
+
+void HTTPClient::OnConnect(const boost::system::error_code &error,
+ boost::asio::ip::tcp::resolver::iterator endpoint_iterator) {
+ if (!error) {
+ TryGet();
+ } else if (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator()) {
+ boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
+ boost::asio::async_connect(sock_, endpoint,
+ std::bind(&HTTPClient::OnConnect, this,
+ std::placeholders::_1,
+ ++endpoint_iterator));
+ } else {
+ assert(!error);
+ }
+}
+
+void HTTPClient::TryGet() {
+ if (counter_->GetWork()) {
+ boost::asio::async_write(sock_, boost::asio::buffer(request_),
+ std::bind(&HTTPClient::OnWrite, this,
+ std::placeholders::_1,
+ std::placeholders::_2));
+ }
+}
+
+void HTTPClient::OnWrite(const boost::system::error_code &error,
+ std::size_t bytes_transferred) {
+ assert(!error);
+ assert(bytes_transferred > 0);
+ std::cout << this << std::endl;
+ TryGet();
+}
+
+int main(int argc, char **argv) {
+ po::options_description desc("Allowed options");
+ desc.add_options()
+ ("help", "produce help message")
+ ("concurrency,c", po::value<int>()->default_value(8),
+ "the number of concurrent connections to use")
+ ("num-requests,n", po::value<int>()->default_value(10000),
+ "the total number of request to make")
+ ("port,p", po::value<int>()->default_value(8000),
+ "the port to connect to")
+ ;
+
+ po::variables_map vm;
+ po::store(po::parse_command_line(argc, argv, desc), vm);
+ po::notify(vm);
+
+ if (vm.count("help")) {
+ std::cout << desc;
+ return 1;
+ }
+
+ boost::asio::io_service io;
+
+ int port = vm["port"].as<int>();
+ boost::asio::ip::tcp::resolver resolver(io);
+ boost::asio::ip::tcp::resolver::query query(
+ "127.0.0.1", boost::lexical_cast<std::string>(port),
+ boost::asio::ip::resolver_query_base::numeric_service);
+ boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(
+ query);
+
+ std::vector<HTTPClient *> clients;
+ RequestCounter counter(vm["num-requests"].as<int>());
+ int concurrency = vm["concurrency"].as<int>();
+ for (int i = 0; i < concurrency; i++) {
+ HTTPClient client(&io, "localhost", port, "/", &counter);
+ client.Run(endpoint_iterator);
+ clients.push_back(&client);
+ }
+ io.run(); // start the main loop
+ return 0;
+}
Oops, something went wrong.

0 comments on commit c94cd5b

Please sign in to comment.