[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-
HandlerFactorymapping, dispatch incoming requests accordingly to the correct type ofRequestHandler.
- For each new connection, create a
SessionObject.
- Parse incoming http requests, and deliver the request to the
Dispatcherto get a response.
- Always be created for every request by a corresponding long-lived
RequestHandlerFactory. - Get important information from the given
NginxConfiglocation 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
RequestHandlershould 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-NginxObjectthat corresponds to the child block in each location config statement. -
All
RequestHandlershould override the pure virtual functionhandle_request, that returns aTrue/Falsestatus.
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
RequestHandlerFactoryshould override the factory methodcreate()that returns astd::shared_ptrpointing to a handler instance. - Currently we assume
create()will not returnnull, i.e. at the level ofRequestHandlerFactoryandRequesthanlder, you may assume that theNginxObjectis semantically valid. However, You will be required to add config validation inconfig_util.h. - (However, we recommend defensive programming. You can
return falseinhandle_requestto 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 HandlerTypevalue inutil.h. For example,NEW_HANDLER = 4. - Register the new handler in the
create_hander_factoryfunction indispatcher.cc. - Register your new handler in
GetHandlerTypeFromTokenfunction inconfig_util.cc. This maps the handler keyword in config to theenumvalue. - 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_teststo the build directory. Runningctestwill run the integration tests.
If you have questions, please feel free to contact us to ask questions or schedule a meeting.
Good Luck! :)
