├── test ├── main.cpp ├── middleware.cpp └── string_buffer.cpp ├── src ├── http │ ├── response.cpp │ ├── response.h │ ├── stream_connection.h │ ├── request.h │ ├── stream_connection.cpp │ ├── connection.h │ └── request.cpp ├── middleware │ ├── middleware.cpp │ ├── route.h │ ├── middleware.h │ ├── middleware_chain.cpp │ ├── router.h │ ├── middleware_chain.h │ ├── router.cpp │ ├── middleware_function.h │ └── route.cpp ├── exceptions │ ├── runtime_error.cpp │ └── runtime_error.h ├── misc │ ├── string_view.h │ ├── string_view.cpp │ ├── putchar_adapter.h │ ├── string_buffer.h │ ├── string_buffer.cpp │ └── putchar_adapter.cpp ├── request_handler.cpp └── request_handler.h ├── .gitignore ├── plan.txt ├── samples ├── get.dump ├── post.dump └── main.cpp └── CMakeLists.txt /test/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch.hpp" 3 | -------------------------------------------------------------------------------- /src/http/response.cpp: -------------------------------------------------------------------------------- 1 | #include "response.h" 2 | 3 | namespace Lighttpning { 4 | 5 | Response::Response(const ConnectionOut& out): 6 | connection(out) 7 | { } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/middleware/middleware.cpp: -------------------------------------------------------------------------------- 1 | #include "middleware.h" 2 | 3 | namespace Lighttpning { 4 | 5 | void Middleware::setNext(const Middleware& middleware) { 6 | next = &middleware; 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/exceptions/runtime_error.cpp: -------------------------------------------------------------------------------- 1 | #include "runtime_error.h" 2 | 3 | namespace Lighttpning { 4 | 5 | RuntimeError::RuntimeError(Cause c): cause(c) { } 6 | 7 | RuntimeError::Cause RuntimeError::what() { 8 | return cause; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/http/response.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "connection.h" 4 | 5 | namespace Lighttpning { 6 | 7 | class Response { 8 | 9 | public: 10 | 11 | Response(const ConnectionOut&); 12 | 13 | private: 14 | 15 | const ConnectionOut& connection; 16 | }; 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary files 2 | *.swp 3 | .DS_Store 4 | 5 | # IDE/editor specific files 6 | 7 | # CLion 8 | .idea/ 9 | 10 | # Visual Studio Code 11 | .vscode/ 12 | 13 | # KDevelop 14 | .kdev*/ 15 | lighttpning.kdev* 16 | 17 | # vim 18 | .vimrc.* 19 | .ycm_extra_conf.py 20 | 21 | # QtCreator 22 | CMakeLists.txt.user 23 | 24 | # Build folders 25 | build*/ 26 | cmake-*/ 27 | 28 | -------------------------------------------------------------------------------- /src/middleware/route.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "http/request.h" 4 | #include "misc/string_view.h" 5 | 6 | namespace Lighttpning { 7 | 8 | class Route { 9 | 10 | public: 11 | 12 | Route(Request::Method, StringView&& pattern); 13 | 14 | bool match(Request&) const; 15 | 16 | private: 17 | 18 | const Request::Method method; 19 | const StringView pattern; 20 | 21 | }; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/misc/string_view.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Lighttpning { 6 | 7 | class StringView { 8 | 9 | public: 10 | 11 | StringView(const char* buffer, size_t size); 12 | 13 | StringView(const char* cStr); 14 | 15 | size_t size() const; 16 | 17 | const char* view() const; 18 | 19 | bool includes(const char *ptr) const; 20 | 21 | private: 22 | 23 | const char* viewPtr; 24 | const size_t viewSize; 25 | }; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /plan.txt: -------------------------------------------------------------------------------- 1 | 2 | [ ] Implement HTTP header parser (std::string, boost::regex, w/o optimization) 3 | [ ] Request line 4 | [ ] Headers 5 | [ ] Query string 6 | [ ] Connection (keep-alive, close) 7 | [ ] Cookies (?) 8 | [ ] Write test for all kinds of HTTP requests (methods, header combinations) ensure that parser is working 9 | [ ] OPTIONS 10 | [ ] GET 11 | [ ] HEAD 12 | [ ] POST 13 | [ ] PUT 14 | [ ] PATCH 15 | [ ] DELETE 16 | [ ] TRACE 17 | [ ] CONNECT 18 | [ ] Think about the way how to pass request body to middleware (streams?) 19 | -------------------------------------------------------------------------------- /samples/get.dump: -------------------------------------------------------------------------------- 1 | GET /leds/led_a HTTP/1.1 2 | Host: localhost:3050 3 | Connection: keep-alive 4 | Cache-Control: max-age=0 5 | Upgrade-Insecure-Requests: 1 6 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36 7 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 8 | Accept-Encoding: gzip, deflate, sdch, br 9 | Accept-Language: ru,en-US;q=0.8,en;q=0.6 10 | Cookie: connect.sid=s%3A_WfnWoWIY46FrGOl9vWyg9UYn3Di5PmU.eXCI%2FrFO%2Fm8LBgt05GatnMtrm2QHwMzKHKnYCWNBkk4 11 | 12 | -------------------------------------------------------------------------------- /samples/post.dump: -------------------------------------------------------------------------------- 1 | POST /leds/led_b/off HTTP/1.1 2 | Host: localhost:3050 3 | Connection: keep-alive 4 | Cache-Control: max-age=0 5 | Upgrade-Insecure-Requests: 1 6 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36 7 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 8 | Accept-Encoding: gzip, deflate, sdch, br 9 | Accept-Language: ru,en-US;q=0.8,en;q=0.6 10 | Cookie: connect.sid=s%3A_WfnWoWIY46FrGOl9vWyg9UYn3Di5PmU.eXCI%2FrFO%2Fm8LBgt05GatnMtrm2QHwMzKHKnYCWNBkk4 11 | 12 | -------------------------------------------------------------------------------- /src/middleware/middleware.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "http/request.h" 4 | #include "http/response.h" 5 | 6 | namespace Lighttpning { 7 | 8 | class Middleware { 9 | 10 | public: 11 | 12 | virtual ~Middleware() { } 13 | 14 | virtual void call(Request&, Response&) const = 0; 15 | 16 | void setNext(const Middleware&); 17 | 18 | class Next { 19 | public: 20 | virtual void operator ()() const = 0; 21 | }; 22 | 23 | protected: 24 | 25 | const Middleware* next = nullptr; 26 | 27 | }; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/request_handler.cpp: -------------------------------------------------------------------------------- 1 | #include "request_handler.h" 2 | 3 | namespace Lighttpning { 4 | 5 | RequestHandler::~RequestHandler() { 6 | for (auto middleware : owned) { 7 | delete middleware; 8 | } 9 | } 10 | 11 | void RequestHandler::handle(Connection& connection) { 12 | Request request(connection); 13 | Response response(connection); 14 | call(request, response); 15 | } 16 | 17 | RequestHandler& RequestHandler::use(Middleware& middleware) { 18 | MiddlewareChain::use(middleware); 19 | return *this; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/exceptions/runtime_error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Lighttpning { 4 | 5 | class RuntimeError { 6 | 7 | public: 8 | 9 | enum Cause { 10 | UNKNOWN, 11 | OUT_OF_MEMORY, 12 | BAD_ARGUMENT 13 | }; 14 | 15 | RuntimeError(Cause); 16 | 17 | Cause what(); 18 | 19 | static inline RuntimeError OutOfMemory() { 20 | return RuntimeError(Cause::OUT_OF_MEMORY); 21 | } 22 | 23 | static inline RuntimeError BadArgument() { 24 | return RuntimeError(Cause::BAD_ARGUMENT); 25 | } 26 | 27 | private: 28 | Cause cause; 29 | }; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/misc/string_view.cpp: -------------------------------------------------------------------------------- 1 | #include "string_view.h" 2 | 3 | #include 4 | 5 | namespace Lighttpning { 6 | 7 | StringView::StringView(const char* buffer, size_t size): 8 | viewPtr(buffer), 9 | viewSize(size) 10 | { } 11 | 12 | StringView::StringView(const char* cStr): 13 | StringView(cStr, strlen(cStr)) 14 | { } 15 | 16 | size_t StringView::size() const { 17 | return viewSize; 18 | } 19 | 20 | const char* StringView::view() const { 21 | return viewPtr; 22 | } 23 | 24 | bool StringView::includes(const char *ptr) const { 25 | return viewPtr <= ptr && ptr < (viewPtr + viewSize); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/misc/putchar_adapter.h: -------------------------------------------------------------------------------- 1 | // #pragma once 2 | // 3 | // #include 4 | // #include 5 | // #include 6 | // 7 | // #include "http_context.h" 8 | // 9 | // namespace lighttpning { 10 | // 11 | // class PutcharAdapter { 12 | // public: 13 | // 14 | // HttpContext* putchar(const char); 15 | // 16 | // private: 17 | // 18 | // static const std::regex request_line_regex; 19 | // static const std::regex header_line_regex; 20 | // static const std::unordered_map method_map; 21 | // 22 | // static void parseLine(HttpContext&, const std::string&); 23 | // 24 | // HttpContext* ctx = nullptr; 25 | // std::string line; 26 | // }; 27 | // 28 | // } 29 | -------------------------------------------------------------------------------- /src/middleware/middleware_chain.cpp: -------------------------------------------------------------------------------- 1 | #include "middleware_chain.h" 2 | 3 | namespace Lighttpning { 4 | 5 | MiddlewareChain::~MiddlewareChain() { 6 | for (auto middleware : owned) { 7 | delete middleware; 8 | } 9 | } 10 | 11 | MiddlewareChain& MiddlewareChain::use(Middleware& middleware) { 12 | 13 | if (chain.size() > 0) { 14 | chain.back()->setNext(middleware); 15 | } 16 | 17 | chain.push_back(&middleware); 18 | 19 | return *this; 20 | } 21 | 22 | void MiddlewareChain::call(Request& request, Response& response) const { 23 | if (chain.size() > 0) { 24 | chain.back()->setNext(*next); 25 | chain.front()->call(request, response); 26 | } else if (next) { 27 | next->call(request, response); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/middleware/router.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "middleware.h" 4 | #include "middleware_chain.h" 5 | #include "route.h" 6 | 7 | #include 8 | 9 | namespace Lighttpning { 10 | 11 | class Router : public Middleware { 12 | 13 | public: 14 | 15 | ~Router(); 16 | 17 | MiddlewareChain& route(Request::Method, StringView&& pattern); 18 | 19 | template Router& route( 20 | Request::Method method, 21 | StringView&& pattern, 22 | const FillerFunction& filler 23 | ) { 24 | filler(route(method, std::move(pattern))); 25 | return *this; 26 | } 27 | 28 | void call(Request&, Response&) const override; 29 | 30 | private: 31 | 32 | std::unordered_map routes; 33 | 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/http/stream_connection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "connection.h" 4 | 5 | #include 6 | 7 | namespace Lighttpning { 8 | 9 | class StreamConnection : public Connection { 10 | public: 11 | StreamConnection(std::istream& in, std::ostream& out); 12 | StreamConnection(std::iostream& inout); 13 | 14 | size_t read(char* buffer, const size_t length) override; 15 | size_t read(char* buffer, const size_t length, const char delimiter) override; 16 | size_t skip(const size_t length) override; 17 | size_t skip(const size_t length, const char delimiter) override; 18 | bool connected() override; 19 | 20 | size_t write(const char* buffer, const size_t length) override; 21 | void close() override; 22 | 23 | private: 24 | std::istream& input; 25 | std::ostream& output; 26 | bool isConnected = true; 27 | }; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/middleware/middleware_chain.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "middleware.h" 4 | #include "middleware_function.h" 5 | 6 | #include 7 | 8 | namespace Lighttpning { 9 | 10 | class MiddlewareChain : public Middleware { 11 | 12 | public: 13 | 14 | ~MiddlewareChain(); 15 | 16 | MiddlewareChain& use(Middleware&); 17 | 18 | template 19 | MiddlewareChain& use(Function&& function) { 20 | auto middleware = new MiddlewareFunction(std::forward(function)); 21 | owned.push_back(middleware); 22 | return use((Middleware&)*middleware); // explicit cast to use non-template overload: use(Middleware&) 23 | } 24 | 25 | void call(Request&, Response&) const override; 26 | 27 | private: 28 | 29 | std::vector chain; 30 | std::vector owned; 31 | }; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/middleware/router.cpp: -------------------------------------------------------------------------------- 1 | #include "router.h" 2 | 3 | namespace Lighttpning { 4 | 5 | Router::~Router() { 6 | for (auto route : routes) { 7 | delete route.first; 8 | delete route.second; 9 | } 10 | } 11 | 12 | MiddlewareChain& Router::route( 13 | Request::Method method, 14 | StringView&& pattern 15 | ) { 16 | auto route = new Route(method, std::move(pattern)); 17 | auto chain = new MiddlewareChain(); 18 | routes.insert({ route, chain }); 19 | return *chain; 20 | } 21 | 22 | void Router::call(Request& request, Response& response) const { 23 | 24 | for (auto route : routes) { 25 | if(route.first->match(request)) { 26 | route.second->setNext(*next); 27 | route.second->call(request, response); 28 | return; // only the first matched route is executed 29 | } 30 | } 31 | 32 | if (next) { 33 | next->call(request, response); 34 | } 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/request_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "middleware/middleware_chain.h" 7 | #include "middleware/router.h" 8 | 9 | namespace Lighttpning { 10 | 11 | class RequestHandler : private MiddlewareChain { 12 | 13 | public: 14 | 15 | ~RequestHandler(); 16 | 17 | void handle(Connection&); 18 | 19 | template 20 | RequestHandler& router(const Function& filler) { 21 | auto router = new Router(); 22 | owned.push_back(router); 23 | filler(*router); 24 | return use((Middleware&)*router); // explicit cast to use non-template overload: use(Middleware&) 25 | } 26 | 27 | RequestHandler& use(Middleware&); 28 | 29 | template 30 | RequestHandler& use(Function&& function) { 31 | MiddlewareChain::use(std::forward(function)); 32 | return *this; 33 | } 34 | 35 | private: 36 | 37 | std::vector owned; 38 | 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/middleware/middleware_function.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "middleware.h" 4 | 5 | namespace Lighttpning { 6 | 7 | template 8 | class MiddlewareFunction : public Middleware { 9 | 10 | public: 11 | 12 | MiddlewareFunction(Function&& function): 13 | func(std::forward(function)) 14 | { } 15 | 16 | void call(Request& request, Response& response) const override { 17 | func(request, response, NextImpl(next, request, response)); 18 | } 19 | 20 | private: 21 | 22 | class NextImpl : public Middleware::Next { 23 | 24 | public: 25 | 26 | NextImpl(const Middleware* middleware, Request& req, Response& res): 27 | next(middleware), 28 | request(req), 29 | response(res) 30 | {} 31 | 32 | void operator ()() const { 33 | if (next) { 34 | next->call(request, response); 35 | } 36 | }; 37 | 38 | private: 39 | 40 | const Middleware* next; 41 | Request& request; 42 | Response& response; 43 | 44 | }; 45 | 46 | const Function&& func; 47 | 48 | }; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /test/middleware.cpp: -------------------------------------------------------------------------------- 1 | 2 | //class Functor { // for tests 3 | 4 | // int val; 5 | 6 | // std::basic_ostream& log() const { 7 | // return std::cout << "<<" << (size_t)this << ">>, val = " << val << ", "; 8 | // } 9 | 10 | //public: 11 | 12 | // //ctor 13 | // Functor(int n): val(n) { 14 | // log() << "CONSTRUCTOR" << std::endl; 15 | // } 16 | // Functor(const Functor& other) { 17 | // val = other.val; 18 | // log() << "COPY CONSTRUCTOR" << std::endl; 19 | // } 20 | // Functor(Functor&& other) { 21 | // val = other.val; 22 | // other.val = 0; 23 | // log() << "MOVE CONSTRUCTOR" << std::endl; 24 | // } 25 | 26 | // //dtor 27 | // ~Functor() { 28 | // log() << "DESTRUCTOR" << std::endl; 29 | // val = 0; 30 | // } 31 | 32 | // //assy 33 | // Functor& operator =(Functor other) { 34 | // val = other.val; 35 | // log() << "COPY ASSY (1)" << std::endl; 36 | // return *this; 37 | // } 38 | // Functor& operator =(const Functor& other) { 39 | // val = other.val; 40 | // log() << "COPY ASSY (2)" << std::endl; 41 | // return *this; 42 | // } 43 | 44 | // //methods 45 | // void operator ()(Request& req, Response& res, const Middleware::Next& next) const { 46 | // log() << "EXEC" << std::endl; 47 | // next(); 48 | // } 49 | //}; 50 | 51 | -------------------------------------------------------------------------------- /samples/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "request_handler.h" 5 | #include "http/stream_connection.h" 6 | 7 | using namespace Lighttpning; 8 | 9 | int main() { 10 | 11 | RequestHandler app; 12 | 13 | app.use([](Request& req, Response& res, const Middleware::Next& next) { 14 | auto path = req.getPath(); 15 | std::cout << "BEGIN " + std::string(path.view(), path.size()) << std::endl; 16 | next(); 17 | std::cout << "END" << std::endl; 18 | }).router([](Router& router) { 19 | router.route(Request::Method::GET, "/leds/$", [](MiddlewareChain& chain) { 20 | chain.use([](Request& req, Response& res, const Middleware::Next& next) { 21 | std::cout << "ROUTE [GET /leds/$] " << std::endl; 22 | next(); 23 | }); 24 | }).route(Request::Method::POST, "/leds/$/$", [](MiddlewareChain& chain) { 25 | chain.use([](Request& req, Response& res, const Middleware::Next& next) { 26 | std::cout << "ROUTE [POST /leds/$/$] " << std::endl; 27 | next(); 28 | }); 29 | }); 30 | }); 31 | 32 | std::fstream getStream("samples/get.dump"); 33 | std::fstream postStream("samples/post.dump"); 34 | StreamConnection getConn(getStream); 35 | StreamConnection postConn(postStream); 36 | app.handle(getConn); 37 | app.handle(postConn); 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(lighttpning) 4 | 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 6 | 7 | set(LIGHTTPNING_SOURCES 8 | src/exceptions/runtime_error.cpp 9 | src/exceptions/runtime_error.h 10 | src/http/connection.h 11 | src/http/request.cpp 12 | src/http/request.h 13 | src/http/response.cpp 14 | src/http/response.h 15 | src/http/stream_connection.cpp 16 | src/http/stream_connection.h 17 | src/middleware/middleware_chain.cpp 18 | src/middleware/middleware_chain.h 19 | src/middleware/middleware_function.h 20 | src/middleware/middleware.cpp 21 | src/middleware/middleware.h 22 | src/middleware/route.cpp 23 | src/middleware/route.h 24 | src/middleware/router.cpp 25 | src/middleware/router.h 26 | src/misc/putchar_adapter.cpp 27 | src/misc/putchar_adapter.h 28 | src/misc/string_buffer.cpp 29 | src/misc/string_buffer.h 30 | src/misc/string_view.cpp 31 | src/misc/string_view.h 32 | src/request_handler.cpp 33 | src/request_handler.h 34 | ) 35 | 36 | set(LIGHTTPNING_TESTS 37 | test/main.cpp 38 | test/string_buffer.cpp 39 | test/middleware.cpp 40 | ) 41 | 42 | include_directories( 43 | src/ 44 | ) 45 | 46 | add_library(liblighttpning ${LIGHTTPNING_SOURCES}) 47 | 48 | add_executable(lighttpning samples/main.cpp) 49 | target_link_libraries (lighttpning liblighttpning) 50 | 51 | add_executable(lighttpning_test ${LIGHTTPNING_TESTS}) 52 | target_link_libraries (lighttpning_test liblighttpning) 53 | 54 | -------------------------------------------------------------------------------- /src/http/request.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "connection.h" 4 | #include "misc/string_buffer.h" 5 | #include "misc/string_view.h" 6 | 7 | #include 8 | 9 | namespace Lighttpning { 10 | 11 | /** 12 | * Represents HTTP request 13 | */ 14 | class Request { 15 | 16 | public: 17 | 18 | /** 19 | * HTTP method 20 | */ 21 | enum class Method : unsigned char { 22 | UNKNOWN, 23 | OPTIONS, 24 | GET, 25 | HEAD, 26 | POST, 27 | PUT, 28 | PATCH, 29 | DELETE, 30 | TRACE, 31 | CONNECT 32 | }; 33 | 34 | /** 35 | * Constructor 36 | */ 37 | Request(ConnectionIn&); 38 | 39 | Method getMethod(); 40 | 41 | const StringView getPath(); 42 | 43 | const StringView& getParameter(size_t index) const; 44 | 45 | size_t addParameter(StringView&& value); 46 | 47 | size_t addParameter(const char* ptr, size_t size); 48 | 49 | private: 50 | 51 | static constexpr size_t DEFAULT_STR_BUFF_SIZE = 64; 52 | static constexpr size_t MAX_HTTP_METHOD_STR_SIZE = 8; 53 | static constexpr size_t MAX_HTTP_PATH_STR_SIZE = 256; 54 | static constexpr size_t MAX_HTTP_VERSION_STR_SIZE = 10; 55 | 56 | ConnectionIn& connection; 57 | 58 | Method method = Method::UNKNOWN; 59 | StringBuffer requestLineBuffer; 60 | StringBuffer headerLineBuffer; 61 | std::vector pathParams; 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /src/http/stream_connection.cpp: -------------------------------------------------------------------------------- 1 | #include "stream_connection.h" 2 | 3 | namespace Lighttpning { 4 | 5 | StreamConnection::StreamConnection(std::istream& in, std::ostream& out): 6 | input(in), 7 | output(out) 8 | { } 9 | 10 | StreamConnection::StreamConnection(std::iostream& inout): 11 | input(inout), 12 | output(inout) 13 | { } 14 | 15 | size_t StreamConnection::read(char* buffer, const size_t length) { 16 | input.read(buffer, length); 17 | return (size_t)input.gcount(); 18 | } 19 | 20 | size_t StreamConnection::read(char* buffer, const size_t length, const char delimiter) { 21 | size_t size = 0; 22 | for (;size < length;) { 23 | char ch = (char)input.get(); 24 | if (input.eof()) { 25 | break; 26 | } 27 | buffer[size++] = ch; 28 | if (ch == delimiter) { 29 | break; 30 | } 31 | } 32 | return size; 33 | } 34 | 35 | size_t StreamConnection::skip(const size_t length) { 36 | input.ignore(length); 37 | return (size_t)input.gcount(); 38 | } 39 | 40 | size_t StreamConnection::skip(const size_t length, const char delimiter) { 41 | input.ignore(length, delimiter); 42 | return (size_t)input.gcount(); 43 | } 44 | 45 | bool StreamConnection::connected() { 46 | return isConnected; 47 | } 48 | 49 | size_t StreamConnection::write(const char* buffer, size_t length) { 50 | return 0; 51 | } 52 | 53 | void StreamConnection::close() { 54 | isConnected = false; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/middleware/route.cpp: -------------------------------------------------------------------------------- 1 | #include "route.h" 2 | 3 | namespace Lighttpning { 4 | 5 | Route::Route(Request::Method method, StringView&& pattern): 6 | method(method), 7 | pattern(std::move(pattern)) 8 | { } 9 | 10 | bool Route::match(Request& request) const { 11 | 12 | bool match = true; 13 | 14 | if (request.getMethod() == method) { 15 | 16 | auto path = request.getPath(); 17 | 18 | const char* patternPtr = pattern.view(); 19 | const char* pathPtr = path.view(); 20 | 21 | while (pattern.includes(patternPtr) && path.includes(pathPtr)) { 22 | 23 | char patternChar = *patternPtr; 24 | char pathChar = *pathPtr; 25 | 26 | if (patternChar == '$') { // parameter placeholder encountered 27 | 28 | size_t len = 0; 29 | 30 | while (path.includes(pathPtr + len)) { 31 | 32 | pathChar = pathPtr[len]; 33 | 34 | if ( 35 | (pathChar >= '0' && pathChar <= '9') || 36 | (pathChar >= 'a' && pathChar <= 'z') || 37 | (pathChar >= 'A' && pathChar <= 'Z') || 38 | (pathChar == '_') // TODO: try isalpha(c), isdigit(c) 39 | ) { 40 | len++; 41 | } else { 42 | break; 43 | } 44 | 45 | } 46 | 47 | if (len > 0) { 48 | request.addParameter(pathPtr, len); 49 | } 50 | 51 | patternPtr++; 52 | pathPtr += len; 53 | 54 | } else if (patternChar != pathChar) { 55 | 56 | match = false; 57 | break; 58 | 59 | } else { 60 | 61 | patternPtr++; 62 | pathPtr++; 63 | 64 | } 65 | 66 | } 67 | 68 | if (pattern.includes(patternPtr) || path.includes(pathPtr)) { 69 | match = false; 70 | } 71 | 72 | } else { 73 | match = false; 74 | } 75 | 76 | return match; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/misc/string_buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Lighttpning { 6 | 7 | class StringBuffer { // TODO: Implement according to The Rule Of Three/Four 8 | 9 | public: 10 | 11 | StringBuffer() = default; 12 | StringBuffer(size_t capacity); 13 | ~StringBuffer(); 14 | 15 | // non-copyable 16 | StringBuffer(const StringBuffer&) = delete; 17 | StringBuffer& operator = (const StringBuffer&) = delete; 18 | 19 | // movable, but not move-assignable 20 | StringBuffer(StringBuffer&&); 21 | StringBuffer& operator = (StringBuffer&&) = delete; 22 | 23 | /** 24 | * @brief Append character to the buffer 25 | */ 26 | void operator += (const char); 27 | 28 | /** 29 | * @brief Append C-string to the buffer 30 | */ 31 | void operator += (const char*); 32 | 33 | /** 34 | * @return True if buffer content is equal to content of other buffer 35 | */ 36 | bool operator == (const StringBuffer&) const; 37 | 38 | /** 39 | * @return True if buffer content is equal to content of C-string 40 | */ 41 | bool operator == (const char*) const; 42 | 43 | /** 44 | * @return True if buffer size is zero 45 | */ 46 | bool empty() const; 47 | 48 | /** 49 | * @brief Set buffer size to zero. 50 | */ 51 | void clear(); 52 | 53 | /** 54 | * @return Buffer capacity 55 | */ 56 | size_t capacity() const; 57 | 58 | /** 59 | * @brief Change buffer capacity 60 | * In case if \p newCapacity is less than buffer size, the latter is reduced to match \p newCapacity 61 | * @param newCapacity 62 | */ 63 | void reserve(size_t newCapacity); 64 | 65 | /** 66 | * @brief Shrink buffer capacity to match buffer size 67 | */ 68 | void shrink(); 69 | 70 | /** 71 | * @return Buffer size 72 | */ 73 | size_t size() const; 74 | 75 | /** 76 | * @brief Change buffer size 77 | * In case if \p newSize is more than buffer capacity, the latter is increased to fit \p newSize 78 | * @param newSize 79 | */ 80 | void resize(size_t newSize); 81 | 82 | /** 83 | * @return Pointer to internal buffer 84 | */ 85 | char* ptr() const; 86 | 87 | private: 88 | 89 | size_t bufferCapacity = 0; 90 | size_t bufferSize = 0; 91 | char* bufferPtr = nullptr; 92 | 93 | }; 94 | 95 | 96 | } 97 | 98 | -------------------------------------------------------------------------------- /src/http/connection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Lighttpning { 6 | 7 | class ConnectionIn { 8 | 9 | public: 10 | 11 | /** 12 | * Read characters from client until \p length characters were read or EOF reached 13 | * @param buffer Buffer to read to 14 | * @param length Maximum number of characters to read 15 | * @return The actual number of characters that have been read 16 | */ 17 | virtual size_t read(char* buffer, const size_t length) = 0; 18 | 19 | /** 20 | * Read characters from client until one of the following occurs: 21 | * - \p delimiter character reached (which is included in resulting \p buffer) 22 | * - \p length characters were read 23 | * - EOF reached 24 | * @param buffer Buffer to read to 25 | * @param length Maximum number of characters to read 26 | * @param delimiter Character to look for 27 | * @return The actual number of characters that have been read 28 | */ 29 | virtual size_t read(char* buffer, const size_t length, const char delimiter) = 0; 30 | 31 | /** 32 | * Skip characters from client until \p length characters were skipped or EOF reached 33 | * @param length Maximum number of characters to skip 34 | * @return The actual number of characters that have been skipped 35 | */ 36 | virtual size_t skip(const size_t length) = 0; 37 | 38 | /** 39 | * Skip characters from client until one of the following events occurs: 40 | * - \p delimiter character reached (which is skipped as well) 41 | * - \p length characters were skipped 42 | * - EOF reached 43 | * @param length Maximum number of characters to skip 44 | * @param delimiter Delimiter character 45 | * @return The actual number of characters that have been skipped 46 | */ 47 | virtual size_t skip(const size_t length, const char delimiter) = 0; 48 | 49 | /** 50 | * @return True if connection is active 51 | */ 52 | virtual bool connected() = 0; 53 | 54 | }; 55 | 56 | class ConnectionOut { 57 | 58 | public: 59 | 60 | /** 61 | * Send characters to client 62 | * @param buffer Buffer to send 63 | * @param length Buffer length 64 | * @return 65 | */ 66 | virtual size_t write(const char* buffer, const size_t length) = 0; 67 | 68 | /** 69 | * Close client connection. No further read/write operations will be possible. 70 | */ 71 | virtual void close() = 0; 72 | }; 73 | 74 | class Connection : public ConnectionIn, public ConnectionOut { }; 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/http/request.cpp: -------------------------------------------------------------------------------- 1 | #include "request.h" 2 | 3 | namespace Lighttpning { 4 | 5 | Request::Request(ConnectionIn& in): 6 | connection(in), 7 | requestLineBuffer(DEFAULT_STR_BUFF_SIZE), 8 | headerLineBuffer(DEFAULT_STR_BUFF_SIZE) 9 | { } 10 | 11 | Request::Method Request::getMethod() { 12 | 13 | if (method == Method::UNKNOWN) { 14 | 15 | // read until space 16 | size_t size = connection.read(requestLineBuffer.ptr(), MAX_HTTP_METHOD_STR_SIZE, ' '); 17 | // set actual size (excluding space character) 18 | requestLineBuffer.resize(size - 1); 19 | 20 | if (requestLineBuffer == "OPTIONS") { 21 | method = Method::OPTIONS; 22 | } else if (requestLineBuffer == "GET") { 23 | method = Method::GET; 24 | } else if (requestLineBuffer == "HEAD") { 25 | method = Method::HEAD; 26 | } else if (requestLineBuffer == "POST") { 27 | method = Method::POST; 28 | } else if (requestLineBuffer == "PUT") { 29 | method = Method::PUT; 30 | } else if (requestLineBuffer == "PATCH") { 31 | method = Method::PATCH; 32 | } else if (requestLineBuffer == "DELETE") { 33 | method = Method::DELETE; 34 | } else if (requestLineBuffer == "TRACE") { 35 | method = Method::TRACE; 36 | } else if (requestLineBuffer == "CONNECT") { 37 | method = Method::CONNECT; 38 | } 39 | 40 | requestLineBuffer.clear(); 41 | 42 | } 43 | 44 | return method; 45 | } 46 | 47 | const StringView Request::getPath() { 48 | 49 | getMethod(); // ensure HTTP method is parsed 50 | 51 | if (requestLineBuffer.empty()) { 52 | 53 | // read until space 54 | size_t size = connection.read(requestLineBuffer.ptr(), MAX_HTTP_PATH_STR_SIZE, ' '); 55 | // set actual size (excluding space character) 56 | requestLineBuffer.resize(size - 1); 57 | 58 | requestLineBuffer.shrink(); 59 | 60 | connection.skip('\n', MAX_HTTP_VERSION_STR_SIZE); 61 | } 62 | 63 | return StringView(requestLineBuffer.ptr(), requestLineBuffer.size()); 64 | } 65 | 66 | const StringView& Request::getParameter(size_t index) const { 67 | return pathParams.at(index); 68 | } 69 | 70 | size_t Request::addParameter(StringView&& value) { 71 | pathParams.push_back(std::move(value)); 72 | return pathParams.size() - 1; 73 | } 74 | 75 | size_t Request::addParameter(const char* ptr, size_t size) { 76 | return addParameter(StringView(ptr, size)); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/misc/string_buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "string_buffer.h" 2 | #include "exceptions/runtime_error.h" 3 | 4 | #include 5 | 6 | namespace Lighttpning { 7 | 8 | StringBuffer::StringBuffer(size_t capacity) { 9 | reserve(capacity); 10 | } 11 | 12 | StringBuffer::~StringBuffer() { 13 | free(bufferPtr); 14 | } 15 | 16 | StringBuffer::StringBuffer(StringBuffer&& other) { 17 | bufferPtr = other.bufferPtr; 18 | bufferCapacity = other.bufferCapacity; 19 | bufferSize = other.bufferSize; 20 | other.bufferPtr = nullptr; 21 | other.bufferCapacity = 0; 22 | other.bufferSize = 0; 23 | } 24 | 25 | bool StringBuffer::empty() const { 26 | return bufferSize == 0; 27 | } 28 | 29 | void StringBuffer::clear() { 30 | bufferSize = 0; 31 | } 32 | 33 | void StringBuffer::operator += (const char ch) { 34 | size_t newSize = bufferSize + 1; 35 | if (bufferCapacity < newSize) { 36 | reserve(newSize); 37 | } 38 | bufferSize = newSize; 39 | bufferPtr[bufferSize] = ch; 40 | } 41 | 42 | void StringBuffer::operator += (const char* cStr) { 43 | size_t lenght = strlen(cStr); 44 | size_t newSize = bufferSize + lenght; 45 | if (bufferCapacity < newSize) { 46 | reserve(newSize); 47 | } 48 | memcpy(bufferPtr + bufferSize, cStr, lenght); 49 | bufferSize = newSize; 50 | } 51 | 52 | bool StringBuffer::operator == (const StringBuffer& other) const { 53 | return strncmp(bufferPtr, other.ptr(), bufferSize) == 0; 54 | } 55 | 56 | bool StringBuffer::operator == (const char* other) const { 57 | return strncmp(bufferPtr, other, bufferSize) == 0; 58 | } 59 | 60 | size_t StringBuffer::capacity() const { 61 | return bufferCapacity; 62 | } 63 | 64 | void StringBuffer::reserve(size_t newCapacity) { 65 | 66 | if (newCapacity > 0) { 67 | 68 | char* newBuffer = (char*)realloc(bufferPtr, newCapacity); 69 | 70 | if (newBuffer == nullptr) { 71 | throw RuntimeError::OutOfMemory(); 72 | } 73 | 74 | bufferPtr = newBuffer; 75 | 76 | bufferCapacity = newCapacity; 77 | if (bufferSize > bufferCapacity) { 78 | bufferSize = bufferCapacity; 79 | } 80 | 81 | } else { // newCapacity =< 0 82 | throw RuntimeError::BadArgument(); 83 | } 84 | 85 | } 86 | 87 | void StringBuffer::shrink() { 88 | return reserve(bufferSize); 89 | } 90 | 91 | size_t StringBuffer::size() const { 92 | return bufferSize; 93 | } 94 | 95 | void StringBuffer::resize(size_t newSize) { 96 | 97 | if (newSize > bufferCapacity) { 98 | reserve(newSize); 99 | } 100 | 101 | bufferSize = newSize; 102 | } 103 | 104 | char* StringBuffer::ptr() const { 105 | return bufferPtr; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/misc/putchar_adapter.cpp: -------------------------------------------------------------------------------- 1 | // #include 2 | // 3 | // #include "putchar_adapter.h" 4 | // 5 | // namespace lighttpning { 6 | // 7 | // // initializers 8 | // 9 | // constexpr auto regex_options = std::regex::ECMAScript; 10 | // 11 | // const std::regex PutcharAdapter::request_line_regex("^([A-Z]+) +(\\S+) +HTTP/(.+)$", regex_options); 12 | // 13 | // const std::regex PutcharAdapter::header_line_regex("", regex_options); 14 | // 15 | // const std::unordered_map PutcharAdapter::method_map = { 16 | // { "OPTIONS", Request::Method::OPTIONS }, 17 | // { "GET", Request::Method::GET }, 18 | // { "HEAD", Request::Method::HEAD }, 19 | // { "POST", Request::Method::POST }, 20 | // { "PUT", Request::Method::PUT }, 21 | // { "PATCH", Request::Method::PATCH }, 22 | // { "DELETE", Request::Method::DELETE }, 23 | // { "TRACE", Request::Method::TRACE }, 24 | // { "CONNECT", Request::Method::CONNECT } 25 | // }; 26 | // 27 | // // constructor 28 | // 29 | // HttpContext* PutcharAdapter::putchar(const char c) { 30 | // 31 | // HttpContext* result = nullptr; 32 | // 33 | // // init context if it's not here 34 | // if (!ctx) { 35 | // ctx = new HttpContext(); 36 | // } 37 | // 38 | // if (c == '\n') { 39 | // 40 | // if (line.empty()) { // 2 new lines in a row => headers parsed 41 | // // TODO: decide on message body 42 | // result = ctx; 43 | // ctx = nullptr; 44 | // } else { // header line 45 | // parseLine(*ctx, line); 46 | // line.clear(); 47 | // } 48 | // 49 | // } else if (c != '\r') { // just skip CR (RFC allows that) 50 | // line += c; 51 | // } 52 | // 53 | // return result; 54 | // } 55 | // 56 | // // members 57 | // 58 | // void PutcharAdapter::parseLine(HttpContext& ctx, const std::string& line) { 59 | // 60 | // std::smatch match; 61 | // 62 | // if (ctx.request.method == Request::Method::UNKNOWN) { 63 | // 64 | // // request line 65 | // 66 | // if (std::regex_match(line, match, request_line_regex) && match.size() == 4) { 67 | // 68 | // ctx.request.method = method_map.at(match[1].str()); 69 | // ctx.request.path = match[2].str(); 70 | // ctx.request.httpVer = match[3].str(); 71 | // 72 | // } else { 73 | // throw new std::invalid_argument("Invalid request line"); 74 | // } 75 | // 76 | // } else { 77 | // 78 | // // header line 79 | // 80 | // if (std::regex_match(line, match, header_line_regex) && match.size() == 3) { 81 | // 82 | // auto name = match[1].str(); 83 | // auto value = match[2].str(); 84 | // ctx.request.headers.insert({ name, value }); 85 | // 86 | // } else { 87 | // 88 | // // multi-line header 89 | // 90 | // } 91 | // } 92 | // 93 | // } 94 | // 95 | // } 96 | -------------------------------------------------------------------------------- /test/string_buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "misc/string_buffer.h" 2 | #include "exceptions/runtime_error.h" 3 | 4 | #include "catch.hpp" 5 | 6 | using namespace Lighttpning; 7 | 8 | SCENARIO("StringBuffer is moved or copied") { 9 | 10 | GIVEN("an empty buffer with default (zero) capacity") { 11 | 12 | StringBuffer strbuff; 13 | REQUIRE(strbuff.ptr() == nullptr); 14 | 15 | WHEN("moved") { 16 | 17 | StringBuffer moved(std::move(strbuff)); 18 | 19 | THEN("resulting buffers should point to null") { 20 | REQUIRE(moved.ptr() == nullptr); 21 | } 22 | 23 | } 24 | 25 | 26 | } 27 | 28 | GIVEN("non-empty buffer") { 29 | 30 | StringBuffer strbuff; 31 | strbuff += "Hello world"; 32 | 33 | REQUIRE(strbuff.ptr() != nullptr); 34 | 35 | WHEN("it is moved") { 36 | 37 | char* ptr = strbuff.ptr(); 38 | size_t size = strbuff.size(); 39 | size_t capacity = strbuff.capacity(); 40 | 41 | StringBuffer moved(std::move(strbuff)); 42 | 43 | THEN("all resources should be moved to new object") { 44 | REQUIRE(strbuff.ptr() == nullptr); 45 | REQUIRE(strbuff.size() == 0); 46 | REQUIRE(strbuff.capacity() == 0); 47 | REQUIRE(moved.ptr() == ptr); 48 | REQUIRE(moved.size() == size); 49 | REQUIRE(moved.capacity() == capacity); 50 | } 51 | } 52 | 53 | } 54 | 55 | } 56 | 57 | SCENARIO("StringBuffer can change its capacity and size") { 58 | 59 | GIVEN("an empty buffer with default (zero) capacity") { 60 | 61 | StringBuffer strbuff; 62 | 63 | REQUIRE(strbuff.size() == 0); 64 | REQUIRE(strbuff.capacity() == 0); 65 | 66 | WHEN("the size is increased") { 67 | 68 | size_t newSize = strbuff.size() + 1; 69 | 70 | strbuff.resize(newSize); 71 | 72 | THEN("the size increases, the capacity should be no less than size") { 73 | REQUIRE(strbuff.size() == newSize); 74 | REQUIRE(strbuff.capacity() >= newSize); 75 | } 76 | } 77 | 78 | WHEN("the capacity is increased") { 79 | 80 | size_t newCapacity = strbuff.capacity() + 1; 81 | size_t size = strbuff.size(); 82 | 83 | strbuff.reserve(newCapacity); 84 | 85 | THEN("the capacity increases, the size stays the same") { 86 | REQUIRE(strbuff.size() == size); 87 | REQUIRE(strbuff.capacity() == newCapacity); 88 | } 89 | } 90 | 91 | } 92 | 93 | GIVEN("an empty buffer with capacity of N items") { 94 | 95 | const size_t N = 12; 96 | StringBuffer strbuff(N); 97 | 98 | REQUIRE(strbuff.size() == 0); 99 | REQUIRE(strbuff.capacity() >= N); 100 | 101 | WHEN("the size is increased") { 102 | 103 | AND_WHEN("new size <= capacity") { 104 | 105 | size_t capacity = strbuff.capacity(); 106 | size_t newSize = N - 1; 107 | 108 | strbuff.resize(newSize); 109 | 110 | THEN("the size increases, the capacity stays the same") { 111 | REQUIRE(strbuff.size() == newSize); 112 | REQUIRE(strbuff.capacity() == capacity); 113 | } 114 | } 115 | 116 | AND_WHEN("new size > capacity") { 117 | 118 | size_t newSize = N + 1; 119 | 120 | strbuff.resize(newSize); 121 | 122 | THEN("the size increases, the capacity should be no less than size") { 123 | REQUIRE(strbuff.size() == newSize); 124 | REQUIRE(strbuff.capacity() >= newSize); 125 | } 126 | 127 | } 128 | 129 | } 130 | 131 | WHEN("the capacity is increased") { 132 | 133 | size_t newCapacity = N + 1; 134 | size_t size = strbuff.size(); 135 | 136 | strbuff.reserve(newCapacity); 137 | 138 | THEN("the capacity increases, the size stays the same") { 139 | REQUIRE(strbuff.size() == size); 140 | REQUIRE(strbuff.capacity() == newCapacity); 141 | } 142 | } 143 | 144 | WHEN("the capacity is reduced") { 145 | 146 | size_t newCapacity = N - 1; 147 | size_t size = strbuff.size(); 148 | 149 | strbuff.reserve(newCapacity); 150 | 151 | THEN("the capacity decreases, the size stays the same") { 152 | REQUIRE(strbuff.size() == size); 153 | REQUIRE(strbuff.capacity() == newCapacity); 154 | } 155 | 156 | } 157 | 158 | } 159 | 160 | GIVEN("non-empty buffer with capacity of N items") { 161 | 162 | const size_t N = 32; 163 | StringBuffer strbuff(N); 164 | 165 | REQUIRE(strbuff.capacity() == N); 166 | 167 | char cStr[] = "Hello world"; 168 | size_t cStrSize = strlen(cStr); 169 | strbuff += cStr; 170 | 171 | REQUIRE(strbuff.size() == cStrSize); 172 | REQUIRE(strbuff.capacity() >= cStrSize); 173 | 174 | WHEN("the size is increased") { 175 | 176 | size_t capacity = strbuff.capacity(); 177 | 178 | AND_WHEN("new size <= capacity") { 179 | 180 | size_t newSize = capacity - 1; 181 | 182 | strbuff.resize(newSize); 183 | 184 | THEN("the size increases, the capacity stays the same") { 185 | REQUIRE(strbuff.size() == newSize); 186 | REQUIRE(strbuff.capacity() == capacity); 187 | } 188 | } 189 | 190 | AND_WHEN("new size > capacity") { 191 | 192 | size_t newSize = capacity + 1; 193 | 194 | strbuff.resize(newSize); 195 | 196 | THEN("the size and capacity both increases") { 197 | REQUIRE(strbuff.size() == newSize); 198 | REQUIRE(strbuff.capacity() >= newSize); 199 | } 200 | 201 | } 202 | 203 | } 204 | 205 | WHEN("the size is reduced") { 206 | 207 | size_t capacity = strbuff.capacity(); 208 | size_t newSize = strbuff.size() - 1; 209 | 210 | strbuff.resize(newSize); 211 | 212 | THEN("the size reduces, the capacity stays the same") { 213 | REQUIRE(strbuff.size() == newSize); 214 | REQUIRE(strbuff.capacity() == capacity); 215 | } 216 | 217 | } 218 | 219 | WHEN("the capacity is increased") { 220 | 221 | size_t newCapacity = strbuff.capacity() + 1; 222 | size_t size = strbuff.size(); 223 | 224 | strbuff.reserve(newCapacity); 225 | 226 | THEN("the capacity increases, the size stays the same") { 227 | REQUIRE(strbuff.size() == size); 228 | REQUIRE(strbuff.capacity() == newCapacity); 229 | } 230 | } 231 | 232 | WHEN("the capacity is reduced") { 233 | 234 | size_t size = strbuff.size(); 235 | 236 | AND_WHEN("new capacity >= size") { 237 | 238 | size_t newCapacity = size + 1; 239 | 240 | strbuff.reserve(newCapacity); 241 | 242 | THEN("the capacity decreases, the size stays the same") { 243 | REQUIRE(strbuff.size() == size); 244 | REQUIRE(strbuff.capacity() == newCapacity); 245 | } 246 | } 247 | 248 | 249 | AND_WHEN("new capacity < size") { 250 | 251 | size_t newCapacity = size - 1; 252 | 253 | strbuff.reserve(newCapacity); 254 | 255 | THEN("the size and capacity both decreases") { 256 | REQUIRE(strbuff.size() == newCapacity); 257 | REQUIRE(strbuff.capacity() == newCapacity); 258 | } 259 | } 260 | 261 | } 262 | 263 | } 264 | 265 | } 266 | --------------------------------------------------------------------------------