[toc]
If you need a step-by-step guide on how to add a request handler, see this.
The Config format, RequestHandler
API, and the dispatching mechanism follows the assignment 6 common API.
This is a high-level overview of the contract of each module.
- Given a location-to-
HandlerFactory
mapping, dispatch incoming requests accordingly to the correct type ofRequestHandler
.
- For each new connection, create a
Session
Object.
- Parse incoming http requests, and deliver the request to the
Dispatcher
to get a response.
- Always be created for every request by a corresponding long-lived
RequestHandlerFactory
. - Get important information from the given
NginxConfig
location config. - Process the request by returning a response.
- General utilities, such as
enum
,constants
,and reusable functions are places inutil.h
. - Config Validation functions are placed in
config_utils.h
, scoped by namespaceconfig_util::
.
-
Build and Run All tests
$ mkdir build $ cd build $ cmake .. $ make && make test
-
Run the server locally
In the build directory:
./bin/server <path to your server config>
-
Local Docker build
In base dir:
$ docker build -f docker/base.Dockerfile -t sudo-rm-rf:base . $ docker build -f docker/Dockerfile -t local_run .
-
Test build on Google cloud
gcloud builds submit --config docker/cloudbuild.yaml .
Please follow the following steps to add a new request handler:
In a new file:
-
Define a new subclass of
RequestHandler
. The base class is inRequestHandler.h
. -
All
RequestHandler
should have a unified constructor:NewHandler(const std::string &location, const NginxConfig &config_block);
location
: The request path in the request URL that will be matched to this handler.config_block
: The sub-NginxObject
that corresponds to the child block in each location config statement. -
All
RequestHandler
should override the pure virtual functionhandle_request
, that returns aTrue/False
status.
Example:
class StaticRequestHandler : public RequestHandler {
public:
StaticRequestHandler(const std::string& location, const NginxConfig& config_block); // In cntr, Fill in the private data by reading the NginxConfig
status handle_request( const http::request<http::string_body>& request,
http::response<http::string_body>& response) override;
private: // Add as many private data needed for processing the request
std::string request_path_;
std::optional<std::string> base_dir_ = std::nullopt;
};
In the same file:
- Define a new subclass of
RequestHandlerFactory
. The base class is inRequestHandlerFactory.h
. - All
RequestHandlerFactory
should override the factory methodcreate()
that returns astd::shared_ptr
pointing to a handler instance. - Currently we assume
create()
will not returnnull
, i.e. at the level ofRequestHandlerFactory
andRequesthanlder
, you may assume that theNginxObject
is semantically valid. However, You will be required to add config validation inconfig_util.h
. - (However, we recommend defensive programming. You can
return false
inhandle_request
to indicate invalid config).
Example:
// Factory
class StaticHandlerFactory : public RequestHandlerFactory {
public:
StaticHandlerFactory(const std::string &location, const NginxConfig &config_block)
: RequestHandlerFactory(location, config_block) {}
std::shared_ptr<RequestHandler> create() {
return std::make_shared<StaticRequestHandler>(location_, config_block_);
// location_ and config_block_ are protected data members of the base Factory
}
};
Before moving on to the next step, you should also have created unit tests for the new pair of RequestHandler
and RequestHandlerFactory
. We recommend adding the unit tests along with the implementation, so that the unit tests serve as a contract specification.
- Add a new
enum HandlerType
value inutil.h
. For example,NEW_HANDLER = 4
. - Register the new handler in the
create_hander_factory
function indispatcher.cc
. - Register your new handler in
GetHandlerTypeFromToken
function inconfig_util.cc
. This maps the handler keyword in config to theenum
value. - Register the new handler file and the tests in
CMakelist.txt
. Include the tests in coverage report.
If the new handler uses a different location config semantics (e.g. additional keywords; additional required statement), you may need to add config validation logic to the helper function ValidateLocationBlock
in config_util.h
.
The signature of the ValidateLocationBlock
is:
bool ValidateLocationBlock(NginxConfig location_config, HandlerType type);
location_config
: The sub-NginxObject
that corresponds to the child block in the location statement.
type
: The enum HandlerType
registered in Util.h
.
example:
bool ValidateLocationBlock(NginxConfig location_config, HandlerType type){
switch (type) {
case UNDEFINED_HANDLER:
return false;
break;
case STATIC_HANDLER: // Validate the location config for your new handler.
if(!config_util::getBaseDirFromLocationConfig(location_config).has_value()){
return false;
}
break;
default:
break;
}
return true;
}
Finally, add some Integration Tests. Some important information to know:
- All integration test scripts reside in
tests/integration_tests
. - When running
make
, cmake will copy all the files recursively inintegration_tests
to the build directory. Runningctest
will run the integration tests.
If you have questions, please feel free to contact us to ask questions or schedule a meeting.
Good Luck! :)