HTTP-Based Service Transport Layer
Basic components:
- Request Line
- Request Head
- [EMPTY LINE]
- Request Body
GET Method Message Example (Dynamic Router):
GET /user/729?tag=student HTTP/1.1 // path params (for router) + query params (for HttpServer)
Host: 127.0.0.1:9090
User-Agent: Mozilla/5.0 Chrome
Accept: application/json
Cookie: token=abcd1234
/* EMPTY LINE */Note that the ?tag=student in the request line corresponds to query parameters: {"tag" : "student"}.
POST Method Message Example (Static Router):
POST /login HTTP/1.1
Host: 127.0.0.1:9090
Content-Type: application/json
Content-Length: 55
Accept: */*
/* EMPTY LINE */
{"username":"root","password":"12345678"} // body params (for HttpServer)HTTP Message Parsing and Encapsulation Module
-
HttpContext -
HttpRequest -
HttpResponse -
HttpServer
Routing Module
Route Key = Method + URI
- Static Matching (use hash tables to store):
handlers_
| Key | Value |
|---|---|
| ("GET","/login") | HandlerPtr A |
| ("POST","/login") | HandlerPtr B |
| ("GET","/user/list") | HandlerPtr C |
callbacks_
| Key | Value |
|---|---|
| ("GET","/login") | loginPageCallback |
| ("POST","/login") | loginSubmitCallback |
| ("GET","/user/list") | getUserListCallback |
- Dynamic Matching (use vectors to store)
regexHandlers_
| Index | Method | Reg. Expr. |
|---|---|---|
| 0 | GET | ^/user/(\d+)$ |
| 1 | GET | ^/article/(\d+)$ |
regexCallbacks_
| Index | Callback Func. |
|---|---|
| 0 | getUserDetailCallback |
| 1 | getArticleCallback |
- The Procedure of Register:
addRegexHandler("GET", "user/:uid", [HanlderPtr])
-> convertToRegex converts :uid to ^/user/([^/]+)$
-> ("GET", ^/user/([^/]+)$, [HanlderPtr]) is stored in std::vector<RouteHandlerObj> regexHandlers_
- The Procedure of
route():
RouteKey{GET, "/user/666"}
-> execute .find() in handlers_ & callbacks_
-> FAILED
-> traverse the regexHandlers_ (check method + regx)
-> if (matched) using check method & regex_match()
-> extractPathParameters(&matchResult, &request)
-> execute request.setPathParameters("param1", "666")
Lastly, the router have req.pathParams["param1"] = "666" and can call the handler method (where it can access the path parameters) in the object that [HandlerPtr] points to.
Note that if
user/666/777, then we also havereq.pathParams["param2"] = "777".
Session Management Module
How does the HTTP server remember the clients?
What user status information does the server need to retain?
Can we verify a user's access permissions based on their information we have on record?
Basic Classes:
- Session (the atomic component)
Containing:
sessionId_data_: a session-specific KV container that stores user information and temporary page datamaxAge_&expiryTime_: checked byisExpired()that is accessible toSessionManager&SessionStoragesessionManager_: to update the stored sessions by indirectly accessing SessionStorage
- SessionManager (create, search, destroy Sessions)
Responsible for managing:
sessionIdSession&SessionStorageobjects
Note:
- The only class have direct access to
SessionStorage. - Can access cookies from
HttpRequestto getsessionId
- SessionStorage (abstract) & MemorySessionStorage (memory implementation)
It maintains a std::unordered_map<std::string, std::shared_ptr<Session>> sessions_.
Is able to:
save(session)load(sessionId)(search corresponding sessions)remove(sessionId)
Handlers → ChatServer → HttpServer → SessionManager → SessionStorage
↑
Session ─── updateSession ┘Middleware Module
We need to preprocess the HttpRequest (parsing, validation and verification) and choose which of them can be sent to Router.
We need to format the HttpResponse (code, msg, data).
Need to manage logging.
Need error handling.
-
Base class:
Middleware; Child class:CorsMiddleware -
CorsMiddlewareoverwritebefore()&after()and is only referenced byChatServer;
CorsMiddleware first checks if Method == "OPTIONS" and handle the preflight request. If yes, it then verifies the Origin, Method and Headers of the preflight request against allowedMethods and allowedHeaders. If all checks pass, the CorsMiddleware writes all CORS-related headers to the response, returns early to skip the Router.
For the preflight request:
addCorsHeaders(response, request.getHeader("Origin")); // add it back the way it wasFor the normal requests:
addCorsHeaders(response, "*"); // default configuration-
CorsConfigrecordsallowedOrigins,allowedMethodsandallowedHeaders -
MiddlewareChainusesvector<shared_ptr<Middleware>>to storeMiddlewares and call theirbefore()&after()in a special order:
A.before → B.before → Handlers → B.after → A.afterIntegrated Database Connection Pool Module
Database is a disk, I/O operation is resource-consuming. Therefore, we need cache system (Redis or memory cache, usually in KV entries), or MySQL (in 2D tables).
Connection pool allows applications to reuse an existing database connection.
The Process of Executing an SQL Statement:
- Establish a connection via the TCP 3-way handshake
- MySQL authenticates the connection and allocates the necessary resources
- Execute the SQL statement
- Close the MySQL connection and reclaim the necessary resources
- Close the connection via the TCP 4-way handshake
How a Database Connection Pool Works:
- Create a connection pool (
vector,queue, etc.) - Manage the usage of the connection (available? -> max connections? -> create a new connection / wait -> max time? -> exception)
- Close the coonection pool (close all connections, free all resources)
Handlers / ChatServer
↓
MysqlUtil
↓
DbConnectionPool(Global Singleton Pattern)
↓
DbConnection(single MySQL connection)
↓
MySQLIn DbConnection:
- Initialize database connection (constructor):
try {
sql::mysql:MySQL_Driver* driver = sql::mysql::get_mysql_driver_instance();
conn_.reset(dirver->connect(host_, user_, password_));
conn_->setSchema(database_);
} catch (const sql::SQLException& e) {
throw DbException(e.what());
}- Two critical functions:
executeQuery(sql, args...); // SELECT
return stmt->executeQuery();
executeUpdate(sql, args...); // INSERT / UPDATE / DELETE
return stmt->executeUpdate();- Use mutex lock while accessing MySQL:
std::lock_guard<std::mutex> lock(mutex_);- It also provides
ping(),reconnect(),cleanup(), for healthy check, reconnection and cleaning up resources.
DbConnectionPool is a process-level singleton instance
- Singleton design pattern:
static DbConnectionPool& getInstance() {
static DbConnectionPool instance;
return instance;
}
/* ... */
private:
DbConnectionPool();
~DbConnectionPool();
/* ... */- Get access to a connection, if none, wait:
// lock(mutex_) first
auto conn = connections_.front();
connections_.pop(); // it's a queue
lock.unlock()
/* check connection and reconnect if conn->ping() failed */- Use
conn->ping()to check connection.
MysqlUtil is a tool class, providing simple APIs to access the database
-
static void init(host, user, password, database, poolSize) {} -
sql::ResultSet* executeQuery(sql, args) {} -
int executeUpdate(sql, args) {}
HTTPS Module
HTTP -- SSL (encode) --> HTTPS (safer, more robust)Hybrid encryption:
Client --visit port 443--> Server
Client <--send CA certificate (contains a **public key**)-- Server
Client → **verify** successfully → generate a **random key** → **encode** the generated key using the public key
Client --send the encoded random key--> Server
Server → decode using the **private key** → gain the random key → use this key for **symmetric encryption** communicationPros:
- Data Security
- Search Engine Optimization
- User Trust
Cons:
- Performance Overhead
- Certificate Costs
- Configuration Complexity & Compatibility Issues
SslConfig:
- Certificate configs
- Protocol version & cipher list configs
- Client verification configs
- Session configs
SslContext:
- Initialize SSL Context
- Load certificate and private key
How to integrate SSL into the HTTP server?
We need to:
- Initialize SSL context
createSslConfig
--init->SslContext
-> load certificate & private key - Manage SSL connection
create
SslConnection
-> invokestartHandshake
--handshake finished-> handle r/w of the encrypted data - Configurate the server
HttpServer::start
--init ssl ctx-> useSslConnection
SslConnection (manage SSL connection, including handshake event & data encryption/decryption).
HttpServer needs to:
- Initialize SSL-related configs
void HttpServer::start(certFile, keyFile) {
ssl::SslConfig sslConfig(certFile, keyFile);
sslContext_ = ssl:: SslContext(sslConfig);
/*
1. init server and start listening
2. create SslConnection when connection is established
*/
}- Create
SslConnectionobject when new connection is established
void HttpServer::onConnection(const muduo::net::TcpConnectionPtr& conn);- Determine if HTTPS service is needed everytime when new message is received
void HttpServer::onMessage(const muduo::net::TcpConnectionPtr& conn,
muduo::net::Buffer* buf,
muduo::Timestamp receiveTime);Notes:
- Reactor design pattern (epoll loop -> SslConnection -> Http parsing / Router)
- muduo C++ library
What's Next? REST API vs. RESTful API vs. JSON-RPC
Common data formats HTTP protocol can transmit (after the Content-Type variable):
application/json— JSON (used by REST‑API, JSON‑RPC)application/x‑www‑form‑urlencoded— URL‑encoded form key‑value pairsmultipart/form‑data— Form‑data for file upload and mixed parametersapplication/xml— XML payloadtext/plain— Plain texttext/html— Hypertext markupapplication/protobuf— Binary Protobuf (gRPC over HTTP)application/yaml— YAML‑structured data- Raw binary stream: images, videos, archives
HTTP‑based Interfaces
├─ Legacy Form‑based Interfaces
├─ RPC over‑HTTP (e.g. JSON‑RPC)
└─ REST‑API (general term for HTTP‑and‑JSON interfaces)
├─ Naive‑style HTTP interfaces
(verb‑containing URLs, all‑POST requests)
└─ RESTful API
(interfaces complying with REST architectural constraints)Requirement: fetch the user whose id = 2
Casual‑style REST API
- Endpoint: POST /getUserById
- HTTP method: always POST
- Parameters are placed inside request‑body
{"userId":2}Feature: the URL contains action verb getUserById. One route corresponds to one behaviour.
Standard RESTful API
- Endpoint: GET /users/2
- HTTP method: GET (for resource query)
- No request body; parameters are encoded within the resource URL
Create user: POST /users
Partially update user: PATCH /users/2
Delete user: DELETE /users/2
JSON‑RPC
- Single unified entry‑point: POST /api/rpc
- HTTP method: fixed as POST
- Target function name is written in the method field inside JSON payload
{
"jsonrpc":"2.0",
"id": 1,
"method":"user.getUserById",
"params":{"userId":2}
}RESTful API (Resource‑Oriented Interface‑Design Paradigm)
The standardized version of REST-API; reckon that world is RESOURCES.
JSON-RPC 2.0 (Procedure-Call Interface‑Design Paradigm)
JSON‑RPC can run on top of HTTP, but it does not belong to the REST‑API family; ONLY cares about how to CALL REMOTE FUNCTIONS.