mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
3824eba04d
This commit adds support for handling multiple requests simultaneously by introducing two new functions: `httpd_req_async_handler_begin()` and `httpd_req_async_handler_complete()`. These functions allow creating an asynchronous copy of a request that can be used on a separate thread and marking the asynchronous request as completed, respectively. Additionally, a new flag `for_async_req` has been added to the `httpd_sess_t` struct to indicate if a socket is being used for an asynchronous request and should not be purged from the LRU cache. An example have been added to demonstrate the usage of these new functions. Closes https://github.com/espressif/esp-idf/issues/10594 Signed-off-by: Harshit Malpani <harshit.malpani@espressif.com>
567 lines
20 KiB
C
567 lines
20 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
|
|
#ifndef _HTTPD_PRIV_H_
|
|
#define _HTTPD_PRIV_H_
|
|
|
|
#include <stdbool.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/param.h>
|
|
#include <netinet/in.h>
|
|
#include <esp_log.h>
|
|
#include <esp_err.h>
|
|
|
|
#include <esp_http_server.h>
|
|
#include "osal.h"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#if CONFIG_NEWLIB_NANO_FORMAT
|
|
#define NEWLIB_NANO_COMPAT_FORMAT PRIu32
|
|
#define NEWLIB_NANO_COMPAT_CAST(size_t_var) (uint32_t)size_t_var
|
|
#else
|
|
#define NEWLIB_NANO_COMPAT_FORMAT "zu"
|
|
#define NEWLIB_NANO_COMPAT_CAST(size_t_var) size_t_var
|
|
#endif
|
|
|
|
/* Size of request data block/chunk (not to be confused with chunked encoded data)
|
|
* that is received and parsed in one turn of the parsing process. This should not
|
|
* exceed the scratch buffer size and should at least be 8 bytes */
|
|
#define PARSER_BLOCK_SIZE 128
|
|
|
|
/* Calculate the maximum size needed for the scratch buffer */
|
|
#define HTTPD_SCRATCH_BUF MAX(HTTPD_MAX_REQ_HDR_LEN, HTTPD_MAX_URI_LEN)
|
|
|
|
/* Formats a log string to prepend context function name */
|
|
#define LOG_FMT(x) "%s: " x, __func__
|
|
|
|
/**
|
|
* @brief Thread related data for internal use
|
|
*/
|
|
struct thread_data {
|
|
othread_t handle; /*!< Handle to thread/task */
|
|
enum {
|
|
THREAD_IDLE = 0,
|
|
THREAD_RUNNING,
|
|
THREAD_STOPPING,
|
|
THREAD_STOPPED,
|
|
} status; /*!< State of the thread */
|
|
};
|
|
|
|
/**
|
|
* @brief A database of all the open sockets in the system.
|
|
*/
|
|
struct sock_db {
|
|
int fd; /*!< The file descriptor for this socket */
|
|
void *ctx; /*!< A custom context for this socket */
|
|
bool ignore_sess_ctx_changes; /*!< Flag indicating if session context changes should be ignored */
|
|
void *transport_ctx; /*!< A custom 'transport' context for this socket, to be used by send/recv/pending */
|
|
httpd_handle_t handle; /*!< Server handle */
|
|
httpd_free_ctx_fn_t free_ctx; /*!< Function for freeing the context */
|
|
httpd_free_ctx_fn_t free_transport_ctx; /*!< Function for freeing the 'transport' context */
|
|
httpd_send_func_t send_fn; /*!< Send function for this socket */
|
|
httpd_recv_func_t recv_fn; /*!< Receive function for this socket */
|
|
httpd_pending_func_t pending_fn; /*!< Pending function for this socket */
|
|
uint64_t lru_counter; /*!< LRU Counter indicating when the socket was last used */
|
|
bool lru_socket; /*!< Flag indicating LRU socket */
|
|
char pending_data[PARSER_BLOCK_SIZE]; /*!< Buffer for pending data to be received */
|
|
size_t pending_len; /*!< Length of pending data to be received */
|
|
bool for_async_req; /*!< If true, the socket will not be LRU purged */
|
|
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
|
bool ws_handshake_done; /*!< True if it has done WebSocket handshake (if this socket is a valid WS) */
|
|
bool ws_close; /*!< Set to true to close the socket later (when WS Close frame received) */
|
|
esp_err_t (*ws_handler)(httpd_req_t *r); /*!< WebSocket handler, leave to null if it's not WebSocket */
|
|
bool ws_control_frames; /*!< WebSocket flag indicating that control frames should be passed to user handlers */
|
|
void *ws_user_ctx; /*!< Pointer to user context data which will be available to handler for websocket*/
|
|
#endif
|
|
};
|
|
|
|
/**
|
|
* @brief Auxiliary data structure for use during reception and processing
|
|
* of requests and temporarily keeping responses
|
|
*/
|
|
struct httpd_req_aux {
|
|
struct sock_db *sd; /*!< Pointer to socket database */
|
|
char scratch[HTTPD_SCRATCH_BUF + 1]; /*!< Temporary buffer for our operations (1 byte extra for null termination) */
|
|
size_t remaining_len; /*!< Amount of data remaining to be fetched */
|
|
char *status; /*!< HTTP response's status code */
|
|
char *content_type; /*!< HTTP response's content type */
|
|
bool first_chunk_sent; /*!< Used to indicate if first chunk sent */
|
|
unsigned req_hdrs_count; /*!< Count of total headers in request packet */
|
|
unsigned resp_hdrs_count; /*!< Count of additional headers in response packet */
|
|
struct resp_hdr {
|
|
const char *field;
|
|
const char *value;
|
|
} *resp_hdrs; /*!< Additional headers in response packet */
|
|
struct http_parser_url url_parse_res; /*!< URL parsing result, used for retrieving URL elements */
|
|
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
|
bool ws_handshake_detect; /*!< WebSocket handshake detection flag */
|
|
httpd_ws_type_t ws_type; /*!< WebSocket frame type */
|
|
bool ws_final; /*!< WebSocket FIN bit (final frame or not) */
|
|
uint8_t mask_key[4]; /*!< WebSocket mask key for this payload */
|
|
#endif
|
|
};
|
|
|
|
/**
|
|
* @brief Server data for each instance. This is exposed publicly as
|
|
* httpd_handle_t but internal structure/members are kept private.
|
|
*/
|
|
struct httpd_data {
|
|
httpd_config_t config; /*!< HTTPD server configuration */
|
|
int listen_fd; /*!< Server listener FD */
|
|
int ctrl_fd; /*!< Ctrl message receiver FD */
|
|
#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING
|
|
SemaphoreHandle_t ctrl_sock_semaphore; /*!< Ctrl socket semaphore */
|
|
#endif
|
|
int msg_fd; /*!< Ctrl message sender FD */
|
|
struct thread_data hd_td; /*!< Information for the HTTPD thread */
|
|
struct sock_db *hd_sd; /*!< The socket database */
|
|
int hd_sd_active_count; /*!< The number of the active sockets */
|
|
httpd_uri_t **hd_calls; /*!< Registered URI handlers */
|
|
struct httpd_req hd_req; /*!< The current HTTPD request */
|
|
struct httpd_req_aux hd_req_aux; /*!< Additional data about the HTTPD request kept unexposed */
|
|
uint64_t lru_counter; /*!< LRU counter */
|
|
|
|
/* Array of registered error handler functions */
|
|
httpd_err_handler_func_t *err_handler_fns;
|
|
};
|
|
|
|
/******************* Group : Session Management ********************/
|
|
/** @name Session Management
|
|
* Functions related to HTTP session management
|
|
* @{
|
|
*/
|
|
|
|
// Enum function, which will be called for each session
|
|
typedef int (*httpd_session_enum_function)(struct sock_db *session, void *context);
|
|
|
|
/**
|
|
* @brief Enumerates all sessions
|
|
*
|
|
* @param[in] hd Server instance data
|
|
* @param[in] enum_function Enumeration function, which will be called for each session
|
|
* @param[in] context Context, which will be passed to the enumeration function
|
|
*/
|
|
void httpd_sess_enum(struct httpd_data *hd, httpd_session_enum_function enum_function, void *context);
|
|
|
|
/**
|
|
* @brief Returns next free session slot (fd<0)
|
|
*
|
|
* @param[in] hd Server instance data
|
|
*
|
|
* @return
|
|
* - +VE : Free session slot
|
|
* - NULL: End of iteration
|
|
*/
|
|
struct sock_db *httpd_sess_get_free(struct httpd_data *hd);
|
|
|
|
/**
|
|
* @brief Retrieve a session by its descriptor
|
|
*
|
|
* @param[in] hd Server instance data
|
|
* @param[in] sockfd Socket FD
|
|
* @return pointer into the socket DB, or NULL if not found
|
|
*/
|
|
struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd);
|
|
|
|
/**
|
|
* @brief Delete sessions whose FDs have became invalid.
|
|
* This is a recovery strategy e.g. after select() fails.
|
|
*
|
|
* @param[in] hd Server instance data
|
|
*/
|
|
void httpd_sess_delete_invalid(struct httpd_data *hd);
|
|
|
|
/**
|
|
* @brief Initializes an http session by resetting the sockets database.
|
|
*
|
|
* @param[in] hd Server instance data
|
|
*/
|
|
void httpd_sess_init(struct httpd_data *hd);
|
|
|
|
/**
|
|
* @brief Starts a new session for client requesting connection and adds
|
|
* it's descriptor to the socket database.
|
|
*
|
|
* @param[in] hd Server instance data
|
|
* @param[in] newfd Descriptor of the new client to be added to the session.
|
|
*
|
|
* @return
|
|
* - ESP_OK : on successfully queuing the work
|
|
* - ESP_FAIL : in case of control socket error while sending
|
|
*/
|
|
esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd);
|
|
|
|
/**
|
|
* @brief Processes incoming HTTP requests
|
|
*
|
|
* @param[in] hd Server instance data
|
|
* @param[in] session Session
|
|
*
|
|
* @return
|
|
* - ESP_OK : on successfully receiving, parsing and responding to a request
|
|
* - ESP_FAIL : in case of failure in any of the stages of processing
|
|
*/
|
|
esp_err_t httpd_sess_process(struct httpd_data *hd, struct sock_db *session);
|
|
|
|
/**
|
|
* @brief Remove client descriptor from the session / socket database
|
|
* and close the connection for this client.
|
|
*
|
|
* @param[in] hd Server instance data
|
|
* @param[in] session Session
|
|
*/
|
|
void httpd_sess_delete(struct httpd_data *hd, struct sock_db *session);
|
|
|
|
/**
|
|
* @brief Free session context
|
|
*
|
|
* @param[in] ctx Pointer to session context
|
|
* @param[in] free_fn Free function to call on session context
|
|
*/
|
|
void httpd_sess_free_ctx(void **ctx, httpd_free_ctx_fn_t free_fn);
|
|
|
|
/**
|
|
* @brief Add descriptors present in the socket database to an fdset and
|
|
* update the value of maxfd which are needed by the select function
|
|
* for looking through all available sockets for incoming data.
|
|
*
|
|
* @param[in] hd Server instance data
|
|
* @param[out] fdset File descriptor set to be updated.
|
|
* @param[out] maxfd Maximum value among all file descriptors.
|
|
*/
|
|
void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd);
|
|
|
|
/**
|
|
* @brief Checks if session can accept another connection from new client.
|
|
* If sockets database is full then this returns false.
|
|
*
|
|
* @param[in] hd Server instance data
|
|
*
|
|
* @return True if session can accept new clients
|
|
*/
|
|
bool httpd_is_sess_available(struct httpd_data *hd);
|
|
|
|
/**
|
|
* @brief Checks if session has any pending data/packets
|
|
* for processing
|
|
*
|
|
* This is needed as httpd_unrecv may un-receive next
|
|
* packet in the stream. If only partial packet was
|
|
* received then select() would mark the fd for processing
|
|
* as remaining part of the packet would still be in socket
|
|
* recv queue. But if a complete packet got unreceived
|
|
* then it would not be processed until further data is
|
|
* received on the socket. This is when this function
|
|
* comes in use, as it checks the socket's pending data
|
|
* buffer.
|
|
*
|
|
* @param[in] hd Server instance data
|
|
* @param[in] session Session
|
|
*
|
|
* @return True if there is any pending data
|
|
*/
|
|
bool httpd_sess_pending(struct httpd_data *hd, struct sock_db *session);
|
|
|
|
/**
|
|
* @brief Removes the least recently used client from the session
|
|
*
|
|
* This may be useful if new clients are requesting for connection but
|
|
* max number of connections is reached, in which case the client which
|
|
* is inactive for the longest will be removed from the session.
|
|
*
|
|
* @param[in] hd Server instance data
|
|
*
|
|
* @return
|
|
* - ESP_OK : if session closure initiated successfully
|
|
* - ESP_FAIL : if failed
|
|
*/
|
|
esp_err_t httpd_sess_close_lru(struct httpd_data *hd);
|
|
|
|
/**
|
|
* @brief Closes all sessions
|
|
*
|
|
* @param[in] hd Server instance data
|
|
*
|
|
*/
|
|
void httpd_sess_close_all(struct httpd_data *hd);
|
|
|
|
/** End of Group : Session Management
|
|
* @}
|
|
*/
|
|
|
|
/****************** Group : URI Handling ********************/
|
|
/** @name URI Handling
|
|
* Methods for accessing URI handlers
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* @brief For an HTTP request, searches through all the registered URI handlers
|
|
* and invokes the appropriate one if found
|
|
*
|
|
* @param[in] hd Server instance data for which handler needs to be invoked
|
|
*
|
|
* @return
|
|
* - ESP_OK : if handler found and executed successfully
|
|
* - ESP_FAIL : otherwise
|
|
*/
|
|
esp_err_t httpd_uri(struct httpd_data *hd);
|
|
|
|
/**
|
|
* @brief Unregister all URI handlers
|
|
*
|
|
* @param[in] hd Server instance data
|
|
*/
|
|
void httpd_unregister_all_uri_handlers(struct httpd_data *hd);
|
|
|
|
/**
|
|
* @brief Validates the request to prevent users from calling APIs, that are to
|
|
* be called only inside a URI handler, outside the handler context
|
|
*
|
|
* @param[in] req Pointer to HTTP request that needs to be validated
|
|
*
|
|
* @return
|
|
* - true : if valid request
|
|
* - false : otherwise
|
|
*/
|
|
bool httpd_validate_req_ptr(httpd_req_t *r);
|
|
|
|
/* httpd_validate_req_ptr() adds some overhead to frequently used APIs,
|
|
* and is useful mostly for debugging, so it's preferable to disable
|
|
* the check by default and enable it only if necessary */
|
|
#ifdef CONFIG_HTTPD_VALIDATE_REQ
|
|
#define httpd_valid_req(r) httpd_validate_req_ptr(r)
|
|
#else
|
|
#define httpd_valid_req(r) true
|
|
#endif
|
|
|
|
/** End of Group : URI Handling
|
|
* @}
|
|
*/
|
|
|
|
/****************** Group : Processing ********************/
|
|
/** @name Processing
|
|
* Methods for processing HTTP requests
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* @brief Initiates the processing of HTTP request
|
|
*
|
|
* Receives incoming TCP packet on a socket, then parses the packet as
|
|
* HTTP request and fills httpd_req_t data structure with the extracted
|
|
* URI, headers are ready to be fetched from scratch buffer and calling
|
|
* http_recv() after this reads the body of the request.
|
|
*
|
|
* @param[in] hd Server instance data
|
|
* @param[in] sd Pointer to socket which is needed for receiving TCP packets.
|
|
*
|
|
* @return
|
|
* - ESP_OK : if request packet is valid
|
|
* - ESP_FAIL : otherwise
|
|
*/
|
|
esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd);
|
|
|
|
/**
|
|
* @brief For an HTTP request, resets the resources allocated for it and
|
|
* purges any data left to be received
|
|
*
|
|
* @param[in] hd Server instance data
|
|
*
|
|
* @return
|
|
* - ESP_OK : if request packet deleted and resources cleaned.
|
|
* - ESP_FAIL : otherwise.
|
|
*/
|
|
esp_err_t httpd_req_delete(struct httpd_data *hd);
|
|
|
|
/**
|
|
* @brief For handling HTTP errors by invoking registered
|
|
* error handler function
|
|
*
|
|
* @param[in] req Pointer to the HTTP request for which error occurred
|
|
* @param[in] error Error type
|
|
*
|
|
* @return
|
|
* - ESP_OK : error handled successful
|
|
* - ESP_FAIL : failure indicates that the underlying socket needs to be closed
|
|
*/
|
|
esp_err_t httpd_req_handle_err(httpd_req_t *req, httpd_err_code_t error);
|
|
|
|
/** End of Group : Parsing
|
|
* @}
|
|
*/
|
|
|
|
/****************** Group : Send/Receive ********************/
|
|
/** @name Send and Receive
|
|
* Methods for transmitting and receiving HTTP requests and responses
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* @brief For sending out data in response to an HTTP request.
|
|
*
|
|
* @param[in] req Pointer to the HTTP request for which the response needs to be sent
|
|
* @param[in] buf Pointer to the buffer from where the body of the response is taken
|
|
* @param[in] buf_len Length of the buffer
|
|
*
|
|
* @return
|
|
* - Length of data : if successful
|
|
* - ESP_FAIL : if failed
|
|
*/
|
|
int httpd_send(httpd_req_t *req, const char *buf, size_t buf_len);
|
|
|
|
/**
|
|
* @brief For receiving HTTP request data
|
|
*
|
|
* @note The exposed API httpd_recv() is simply this function with last parameter
|
|
* set as false. This function is used internally during reception and
|
|
* processing of a new request. The option to halt after receiving pending
|
|
* data prevents the server from requesting more data than is needed for
|
|
* completing a packet in case when all the remaining part of the packet is
|
|
* in the pending buffer.
|
|
*
|
|
* @param[in] req Pointer to new HTTP request which only has the socket descriptor
|
|
* @param[out] buf Pointer to the buffer which will be filled with the received data
|
|
* @param[in] buf_len Length of the buffer
|
|
* @param[in] halt_after_pending When set true, halts immediately after receiving from
|
|
* pending buffer
|
|
*
|
|
* @return
|
|
* - Length of data : if successful
|
|
* - ESP_FAIL : if failed
|
|
*/
|
|
int httpd_recv_with_opt(httpd_req_t *r, char *buf, size_t buf_len, bool halt_after_pending);
|
|
|
|
/**
|
|
* @brief For un-receiving HTTP request data
|
|
*
|
|
* This function copies data into internal buffer pending_data so that
|
|
* when httpd_recv is called, it first fetches this pending data and
|
|
* then only starts receiving from the socket
|
|
*
|
|
* @note If data is too large for the internal buffer then only
|
|
* part of the data is unreceived, reflected in the returned
|
|
* length. Make sure that such truncation is checked for and
|
|
* handled properly.
|
|
*
|
|
* @param[in] req Pointer to new HTTP request which only has the socket descriptor
|
|
* @param[in] buf Pointer to the buffer from where data needs to be un-received
|
|
* @param[in] buf_len Length of the buffer
|
|
*
|
|
* @return Length of data copied into pending buffer
|
|
*/
|
|
size_t httpd_unrecv(struct httpd_req *r, const char *buf, size_t buf_len);
|
|
|
|
/**
|
|
* @brief This is the low level default send function of the HTTPD. This should
|
|
* NEVER be called directly. The semantics of this is exactly similar to
|
|
* send() of the BSD socket API.
|
|
*
|
|
* @param[in] hd Server instance data
|
|
* @param[in] sockfd Socket descriptor for sending data
|
|
* @param[in] buf Pointer to the buffer from where the body of the response is taken
|
|
* @param[in] buf_len Length of the buffer
|
|
* @param[in] flags Flags for mode selection
|
|
*
|
|
* @return
|
|
* - Length of data : if successful
|
|
* - -1 : if failed (appropriate errno is set)
|
|
*/
|
|
int httpd_default_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags);
|
|
|
|
/**
|
|
* @brief This is the low level default recv function of the HTTPD. This should
|
|
* NEVER be called directly. The semantics of this is exactly similar to
|
|
* recv() of the BSD socket API.
|
|
*
|
|
* @param[in] hd Server instance data
|
|
* @param[in] sockfd Socket descriptor for sending data
|
|
* @param[out] buf Pointer to the buffer which will be filled with the received data
|
|
* @param[in] buf_len Length of the buffer
|
|
* @param[in] flags Flags for mode selection
|
|
*
|
|
* @return
|
|
* - Length of data : if successful
|
|
* - -1 : if failed (appropriate errno is set)
|
|
*/
|
|
int httpd_default_recv(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, int flags);
|
|
|
|
/** End of Group : Send and Receive
|
|
* @}
|
|
*/
|
|
|
|
/* ************** Group: WebSocket ************** */
|
|
/** @name WebSocket
|
|
* Functions for WebSocket header parsing
|
|
* @{
|
|
*/
|
|
|
|
|
|
/**
|
|
* @brief This function is for responding a WebSocket handshake
|
|
*
|
|
* @param[in] req Pointer to handshake request that will be handled
|
|
* @param[in] supported_subprotocol Pointer to the subprotocol supported by this URI
|
|
* @return
|
|
* - ESP_OK : When handshake is sucessful
|
|
* - ESP_ERR_NOT_FOUND : When some headers (Sec-WebSocket-*) are not found
|
|
* - ESP_ERR_INVALID_VERSION : The WebSocket version is not "13"
|
|
* - ESP_ERR_INVALID_STATE : Handshake was done beforehand
|
|
* - ESP_ERR_INVALID_ARG : Argument is invalid (null or non-WebSocket)
|
|
* - ESP_FAIL : Socket failures
|
|
*/
|
|
esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req, const char *supported_subprotocol);
|
|
|
|
/**
|
|
* @brief This function is for getting a frame type
|
|
* and responding a WebSocket control frame automatically
|
|
*
|
|
* @param[in] req Pointer to handshake request that will be handled
|
|
* @return
|
|
* - ESP_OK : When handshake is sucessful
|
|
* - ESP_ERR_INVALID_ARG : Argument is invalid (null or non-WebSocket)
|
|
* - ESP_ERR_INVALID_STATE : Received only some parts of a control frame
|
|
* - ESP_FAIL : Socket failures
|
|
*/
|
|
esp_err_t httpd_ws_get_frame_type(httpd_req_t *req);
|
|
|
|
/**
|
|
* @brief Trigger an httpd session close externally
|
|
*
|
|
* @note Calling this API is only required in special circumstances wherein
|
|
* some application requires to close an httpd client session asynchronously.
|
|
*
|
|
* @param[in] handle Handle to server returned by httpd_start
|
|
* @param[in] session Session to be closed
|
|
*
|
|
* @return
|
|
* - ESP_OK : On successfully initiating closure
|
|
* - ESP_FAIL : Failure to queue work
|
|
* - ESP_ERR_NOT_FOUND : Socket fd not found
|
|
* - ESP_ERR_INVALID_ARG : Null arguments
|
|
*/
|
|
esp_err_t httpd_sess_trigger_close_(httpd_handle_t handle, struct sock_db *session);
|
|
|
|
/** End of WebSocket related functions
|
|
* @}
|
|
*/
|
|
|
|
/**
|
|
* @brief Function to dispatch events in default event loop
|
|
*
|
|
*/
|
|
void esp_http_server_dispatch_event(int32_t event_id, const void* event_data, size_t event_data_size);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* ! _HTTPD_PRIV_H_ */
|