enabled the http file handler to accept POST, PUT, DELETE

This commit is contained in:
2022-10-10 01:41:07 +09:00
parent 7ec3ba3ab7
commit 855e0fb88e
14 changed files with 294 additions and 217 deletions

View File

@ -43,8 +43,6 @@ enum file_res_mode_t
};
typedef enum file_res_mode_t file_res_mode_t;
#define FILE_PENDING_IO_THRESHOLD 5
#define FILE_OVER_READ_FROM_CLIENT (1 << 0)
#define FILE_OVER_READ_FROM_PEER (1 << 1)
#define FILE_OVER_WRITE_TO_CLIENT (1 << 2)
@ -55,6 +53,7 @@ struct file_t
{
HIO_SVC_HTTS_RSRC_HEADER;
int options;
hio_oow_t num_pending_writes_to_client;
hio_oow_t num_pending_writes_to_peer;
int sendfile_ok;
@ -98,7 +97,7 @@ static void file_halt_participating_devices (file_t* file)
HIO_ASSERT (file->client->htts->hio, file->client != HIO_NULL);
HIO_ASSERT (file->client->htts->hio, file->client->sck != HIO_NULL);
HIO_DEBUG3 (file->client->htts->hio, "HTTS(%p) - file(c=%d,p=%d) Halting participating devices in file state\n", file->client->htts, (int)file->client->sck->hnd, (int)file->peer);
HIO_DEBUG3 (file->client->htts->hio, "HTTS(%p) - file(c=%d,p=%d) Halting participating devices\n", file->client->htts, (int)file->client->sck->hnd, (int)file->peer);
/* only the client socket device.
* the peer side is just a file descriptor - no hio-managed device */
@ -116,11 +115,6 @@ static int file_write_to_client (file_t* file, const void* data, hio_iolen_t dle
return -1;
}
if (file->num_pending_writes_to_client > FILE_PENDING_IO_THRESHOLD)
{
/* STOP READING */
/*if (hio_dev_pro_read(file->peer, HIO_DEV_PRO_OUT, 0) <= -1) return -1;*/
}
return 0;
}
@ -135,11 +129,6 @@ static int file_sendfile_to_client (file_t* file, hio_foff_t foff, hio_iolen_t l
return -1;
}
if (file->num_pending_writes_to_client > FILE_PENDING_IO_THRESHOLD)
{
/* STOP READING */
/*if (hio_dev_pro_read(file->peer, HIO_DEV_PRO_OUT, 0) <= -1) return -1;*/
}
return 0;
}
@ -248,10 +237,20 @@ static int file_write_to_peer (file_t* file, const void* data, hio_iolen_t dlen)
}
else
{
hio_iolen_t pos, rem, n;
if (file->req_method == HIO_HTTP_GET) return 0;
if (file->peer <= -1) return 0; /* peer open proabably failed */
HIO_ASSERT (hio, file->peer >= 0);
return write(file->peer, data, dlen) <= -1? -1: 0;
/* TODO: async file io -> create a file device?? */
pos = 0;
rem = dlen;
while (rem > 0)
{
n = write(file->peer, &((const hio_uint8_t*)data)[pos], rem);
if (n <= -1) return -1;
rem -= n;
pos += n;
}
}
return 0;
@ -334,9 +333,10 @@ static int file_client_on_read (hio_dev_sck_t* sck, const void* buf, hio_iolen_t
goto oops;
}
if (!file->peer)
if (file->peer <= -1)
{
/* the peer is gone */
/* the peer is gone or not even opened */
HIO_DEBUG3 (cli->htts->hio, "HTTS(%p) - file(c=%d,p=%d) read on client, no peer to write\n", file->client->htts, (int)sck->hnd, file->peer);
goto oops; /* do what? just return 0? */
}
@ -344,12 +344,14 @@ static int file_client_on_read (hio_dev_sck_t* sck, const void* buf, hio_iolen_t
{
/* EOF on the client side. arrange to close */
HIO_DEBUG3 (cli->htts->hio, "HTTS(%p) - file(c=%d,p=%d) EOF detected on client\n", file->client->htts, (int)sck->hnd, file->peer);
file->client_eof_detected = 1;
if (!(file->over & FILE_OVER_READ_FROM_CLIENT)) /* if this is true, EOF is received without file_client_htrd_poke() */
{
if (file_write_to_peer(file, HIO_NULL, 0) <= -1) goto oops;
file->client_eof_detected = 1;
int n;
n = file_write_to_peer(file, HIO_NULL, 0);
file_mark_over (file, FILE_OVER_READ_FROM_CLIENT);
if (n <= -1) goto oops;
}
}
else
@ -422,7 +424,6 @@ oops:
return 0;
}
/* --------------------------------------------------------------------- */
static int file_client_htrd_poke (hio_htrd_t* htrd, hio_htre_t* req)
@ -438,7 +439,7 @@ static int file_client_htrd_poke (hio_htrd_t* htrd, hio_htre_t* req)
if (file->req_method != HIO_HTTP_GET)
{
if (file_send_final_status_to_client(file, 200, 0) <= -1) return -1;
if (file_send_final_status_to_client(file, HIO_HTTP_STATUS_OK, 0) <= -1) return -1;
}
file_mark_over (file, FILE_OVER_READ_FROM_CLIENT);
@ -476,7 +477,7 @@ static int file_send_header_to_client (file_t* file, int status_code, int force_
if (!force_close) force_close = !file->keep_alive;
content_length = file->end_offset - file->start_offset + 1;
if (status_code == 200 && file->total_size != content_length) status_code = 206;
if (status_code == HIO_HTTP_STATUS_OK && file->total_size != content_length) status_code = HIO_HTTP_STATUS_PARTIAL_CONTENT;
if (hio_becs_fmt(cli->sbuf, "HTTP/%d.%d %d %hs\r\nServer: %hs\r\nDate: %s\r\nConnection: %hs\r\nAccept-Ranges: bytes\r\nContent-Type: %hs\r\n",
file->req_version.major, file->req_version.minor,
@ -485,7 +486,7 @@ static int file_send_header_to_client (file_t* file, int status_code, int force_
(force_close? "close": "keep-alive"), mime_type) == (hio_oow_t)-1) return -1;
if (file->req_method == HIO_HTTP_GET && hio_becs_fcat(cli->sbuf, "ETag: %hs\r\n", file->peer_etag) == (hio_oow_t)-1) return -1;
if (status_code == 206 && hio_becs_fcat(cli->sbuf, "Content-Ranges: bytes %ju-%ju/%ju\r\n", (hio_uintmax_t)file->start_offset, (hio_uintmax_t)file->end_offset, (hio_uintmax_t)file->total_size) == (hio_oow_t)-1) return -1;
if (status_code == HIO_HTTP_STATUS_PARTIAL_CONTENT && hio_becs_fcat(cli->sbuf, "Content-Ranges: bytes %ju-%ju/%ju\r\n", (hio_uintmax_t)file->start_offset, (hio_uintmax_t)file->end_offset, (hio_uintmax_t)file->total_size) == (hio_oow_t)-1) return -1;
/* ----- */
// TODO: Allow-Contents
@ -566,7 +567,12 @@ static int file_send_contents_to_client (file_t* file)
return 0;
}
static HIO_INLINE int process_range_header (file_t* file, hio_htre_t* req)
#define ERRNO_TO_STATUS_CODE(x) ( \
((x) == ENOENT)? HIO_HTTP_STATUS_NOT_FOUND: \
((x) == EPERM || (x) == EACCES)? HIO_HTTP_STATUS_FORBIDDEN: HIO_HTTP_STATUS_INTERNAL_SERVER_ERROR \
)
static HIO_INLINE int process_range_header (file_t* file, hio_htre_t* req, int* error_status)
{
struct stat st;
const hio_htre_hdrval_t* tmp;
@ -574,14 +580,14 @@ static HIO_INLINE int process_range_header (file_t* file, hio_htre_t* req)
if (fstat(file->peer, &st) <= -1)
{
file_send_final_status_to_client (file, 500, 1);
*error_status = ERRNO_TO_STATUS_CODE(errno);
return -1;
}
if ((st.st_mode & S_IFMT) != S_IFREG)
{
/* TODO: support directory listing if S_IFDIR? still disallow special files. */
file_send_final_status_to_client (file, 403, 1); /* forbidden */
*error_status = HIO_HTTP_STATUS_FORBIDDEN;
return -1;
}
@ -617,7 +623,7 @@ static HIO_INLINE int process_range_header (file_t* file, hio_htre_t* req)
if (hio_parse_http_range_bcstr(tmp->ptr, &range) <= -1)
{
range_not_satisifiable:
file_send_final_status_to_client (file, 416, 1); /* 406 Requested Range Not Satisfiable */
*error_status = HIO_HTTP_STATUS_RANGE_NOT_SATISFIABLE;
return -1;
}
@ -646,7 +652,13 @@ static HIO_INLINE int process_range_header (file_t* file, hio_htre_t* req)
}
if (file->start_offset > 0)
lseek(file->peer, file->start_offset, SEEK_SET);
{
if (lseek(file->peer, file->start_offset, SEEK_SET) <= -1)
{
*error_status = ERRNO_TO_STATUS_CODE(errno);
return -1;
}
}
}
else
@ -660,86 +672,39 @@ static HIO_INLINE int process_range_header (file_t* file, hio_htre_t* req)
return 0;
}
#define ERRNO_TO_STATUS_CODE(x) ( \
((x) == ENOENT)? 404: \
((x) == EPERM || (x) == EACCES)? 403: 500 \
)
static int open_peer (file_t* file, const hio_bch_t* actual_file)
static int open_peer_with_mode (file_t* file, const hio_bch_t* actual_file, int flags, int* error_status)
{
switch (file->req_method)
flags |= O_NONBLOCK;
#if defined(O_CLOEXEC)
flags |= O_CLOEXEC;
#endif
#if defined(O_LARGEFILE)
flags |= O_LARGEFILE;
#endif
file->peer = open(actual_file, flags, 0644);
if (HIO_UNLIKELY(file->peer <= -1))
{
case HIO_HTTP_GET:
case HIO_HTTP_HEAD:
{
int flags;
if (access(actual_file, R_OK) == -1)
{
file_send_final_status_to_client (file, ERRNO_TO_STATUS_CODE(errno), 1); /* 404 not found 403 Forbidden */
return -1;
}
flags = O_RDONLY | O_NONBLOCK;
#if defined(O_CLOEXEC)
flags |= O_CLOEXEC;
#endif
#if defined(O_LARGEFILE)
flags |= O_LARGEFILE;
#endif
file->peer = open(actual_file, flags);
if (HIO_UNLIKELY(file->peer <= -1))
{
file_send_final_status_to_client (file, ERRNO_TO_STATUS_CODE(errno), 1);
return -1;
}
return 0;
}
#if 0
case HIO_HTTP_PUT:
case HIO_HTTP_POST:
/* TOOD: this is destructive. jump to default if not allowed by flags... */
file->peer = open(actual_file, O_WRONLY | O_TRUNC | O_CREAT | O_NONBLOCK | O_CLOEXEC, 0644);
if (HIO_UNLIKELY(file->peer <= -1))
{
file_send_final_status_to_client (file, ERRNO_TO_STATUS_CODE(errno), 1);
return -1;
}
return 0;
case HIO_HTTP_PATCH:
/* TOOD: this is destructive. jump to default if not allowed by flags... */
file->peer = open(actual_file, O_WRONLY | O_NONBLOCK | O_CLOEXEC, 0644);
if (HIO_UNLIKELY(file->peer <= -1))
{
file_send_final_status_to_client (file, ERRNO_TO_STATUS_CODE(errno), 1);
return -1;
}
return 0;
#endif
#if 0
case HIO_HTTP_DELETE:
/* TODO: */
#endif
*error_status = ERRNO_TO_STATUS_CODE(errno);
return -1;
}
file_send_final_status_to_client (file, 405, 1); /* 405: method not allowed */
return -1;
return 0;
}
static HIO_INLINE void fadvise_on_peer (file_t* file)
static HIO_INLINE void set_tcp_cork (hio_dev_sck_t* sck)
{
#if defined(HAVE_POSIX_FADVISE)
if (file->req_method == HIO_HTTP_GET)
posix_fadvise (file->peer, file->start_offset, file->end_offset - file->start_offset + 1, POSIX_FADV_SEQUENTIAL);
#if defined(TCP_CORK)
int tcp_cork = 1;
#if defined(SOL_TCP)
hio_dev_sck_setsockopt (sck, SOL_TCP, TCP_CORK, &tcp_cork, HIO_SIZEOF(tcp_cork));
#elif defined(IPPROTO_TCP)
hio_dev_sck_setsockopt (sck, IPPROTO_TCP, TCP_CORK, &tcp_cork, HIO_SIZEOF(tcp_cork));
#endif
#endif
}
int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* req, const hio_bch_t* docroot, const hio_bch_t* filepath, const hio_bch_t* mime_type)
int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* req, const hio_bch_t* docroot, const hio_bch_t* filepath, const hio_bch_t* mime_type, int options)
{
/* TODO: ETag, Last-Modified... */
@ -747,6 +712,7 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
hio_svc_htts_cli_t* cli = hio_dev_sck_getxtn(csck);
file_t* file = HIO_NULL;
hio_bch_t* actual_file = HIO_NULL;
int status_code;
/* ensure that you call this function before any contents is received */
HIO_ASSERT (hio, hio_htre_getcontentlen(req) == 0);
@ -760,6 +726,7 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
file = (file_t*)hio_svc_htts_rsrc_make(htts, HIO_SIZEOF(*file), file_on_kill);
if (HIO_UNLIKELY(!file)) goto oops;
file->options = options;
file->client = cli;
file->sendfile_ok = hio_dev_sck_sendfileok(cli->sck);
/*file->num_pending_writes_to_client = 0;
@ -781,11 +748,6 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
file->peer_tmridx = HIO_TMRIDX_INVALID;
file->peer = -1;
if (open_peer(file, actual_file) <= -1 ||
process_range_header(file, req) <= -1) goto oops;
fadvise_on_peer (file);
#if !defined(FILE_ALLOW_UNLIMITED_REQ_CONTENT_LENGTH)
if (file->req_content_length_unlimited)
{
@ -794,21 +756,21 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
/* option 1. buffer contents. if it gets too large, send 413 Request Entity Too Large.
* option 2. send 411 Length Required immediately
* option 3. set Content-Length to -1 and use EOF to indicate the end of content [Non-Standard] */
if (file_send_final_status_to_client(file, 411, 1) <= -1) goto oops;
if (file_send_final_status_to_client(file, HIO_HTTP_STATUS_LENGTH_REQUIRED, 1) <= -1) goto oops;
}
#endif
if (req->flags & HIO_HTRE_ATTR_EXPECT100)
{
if (hio_comp_http_version_numbers(&req->version, 1, 1) >= 0 &&
if (!(options & HIO_SVC_HTTS_FILE_NO_100_CONTINUE) &&
hio_comp_http_version_numbers(&req->version, 1, 1) >= 0 &&
(file->req_content_length_unlimited || file->req_content_length > 0) &&
(file->req_method != HIO_HTTP_GET && file->req_method != HIO_HTTP_HEAD))
{
hio_bch_t msgbuf[64];
hio_oow_t msglen;
msglen = hio_fmttobcstr(hio, msgbuf, HIO_COUNTOF(msgbuf), "HTTP/%d.%d 100 Continue\r\n\r\n", file->req_version.major, file->req_version.minor);
msglen = hio_fmttobcstr(hio, msgbuf, HIO_COUNTOF(msgbuf), "HTTP/%d.%d %d %hs\r\n\r\n", file->req_version.major, file->req_version.minor, HIO_HTTP_STATUS_CONTINUE, hio_http_status_to_bcstr(HIO_HTTP_STATUS_CONTINUE));
if (file_write_to_client(file, msgbuf, msglen) <= -1) goto oops;
file->ever_attempted_to_write_to_client = 0; /* reset this as it's polluted for 100 continue */
}
@ -816,7 +778,7 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
else if (req->flags & HIO_HTRE_ATTR_EXPECT)
{
/* 417 Expectation Failed */
file_send_final_status_to_client (file, 417, 1);
file_send_final_status_to_client (file, HIO_HTTP_STATUS_EXPECTATION_FAILED, 1);
goto oops;
}
@ -843,9 +805,15 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
else
{
/* no content to be uploaded from the client */
#if 0
/* indicate EOF to the peer and disable input wathching from the client */
if (file_write_to_peer(file, HIO_NULL, 0) <= -1) goto oops;
HIO_ASSERT (hio, file->over | FILE_OVER_WRITE_TO_PEER); /* must be set by the call to file_write_to_peer() above */
file_mark_over (file, FILE_OVER_READ_FROM_CLIENT);
#else
/* no peer is open yet. so simply set the mars forcibly instead of calling file_write_to_peer() with null data */
file_mark_over (file, FILE_OVER_READ_FROM_CLIENT | FILE_OVER_WRITE_TO_PEER);
#endif
}
#if defined(FILE_ALLOW_UNLIMITED_REQ_CONTENT_LENGTH)
}
@ -863,34 +831,74 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
file->res_mode_to_cli = FILE_RES_MODE_CLOSE;
}
if (file->req_method == HIO_HTTP_GET)
switch (file->req_method)
{
if (file->etag_match)
{
/* 304 not modified */
if (file_send_final_status_to_client(file, 304, 0) <= -1) goto oops;
file_mark_over (file, FILE_OVER_READ_FROM_PEER | FILE_OVER_WRITE_TO_PEER);
}
else
{
/* normal full transfer */
#if defined(TCP_CORK)
int tcp_cork = 1;
#if defined(SOL_TCP)
hio_dev_sck_setsockopt(file->client->sck, SOL_TCP, TCP_CORK, &tcp_cork, HIO_SIZEOF(tcp_cork));
#elif defined(IPPROTO_TCP)
hio_dev_sck_setsockopt(file->client->sck, IPPROTO_TCP, TCP_CORK, &tcp_cork, HIO_SIZEOF(tcp_cork));
#endif
#endif
case HIO_HTTP_GET:
if (open_peer_with_mode(file, actual_file, O_RDONLY, &status_code) <= -1) goto done_with_status_code;
if (process_range_header(file, req, &status_code) <= -1) goto done_with_status_code;
if (file_send_header_to_client(file, 200, 0, mime_type) <= -1 ||
if (file->etag_match)
{
status_code = HIO_HTTP_STATUS_NOT_MODIFIED;
goto done_with_status_code;
}
/* normal full transfer */
#if defined(HAVE_POSIX_FADVISE)
posix_fadvise (file->peer, file->start_offset, file->end_offset - file->start_offset + 1, POSIX_FADV_SEQUENTIAL);
#endif
set_tcp_cork (file->client->sck);
if (file_send_header_to_client(file, HIO_HTTP_STATUS_OK, 0, mime_type) <= -1 ||
file_send_contents_to_client(file) <= -1) goto oops;
}
}
else if (file->req_method == HIO_HTTP_HEAD)
{
if (file_send_header_to_client(file, 200, 0, mime_type) <= -1) goto oops;
file_mark_over (file, FILE_OVER_READ_FROM_PEER | FILE_OVER_WRITE_TO_PEER);
break;
case HIO_HTTP_HEAD:
if (open_peer_with_mode(file, actual_file, O_RDONLY, &status_code) <= -1) goto done_with_status_code;
if (process_range_header(file, req, &status_code) <= -1) goto done_with_status_code;
status_code = HIO_HTTP_STATUS_OK;
goto done_with_status_code;
case HIO_HTTP_POST:
case HIO_HTTP_PUT:
if (file->options & HIO_SVC_HTTS_FILE_READ_ONLY)
{
status_code = HIO_HTTP_STATUS_METHOD_NOT_ALLOWED;
goto done_with_status_code;
}
if (open_peer_with_mode(file, actual_file, O_WRONLY | O_TRUNC | O_CREAT, &status_code) <= -1) goto done_with_status_code;
/* the client input must be written to the peer side */
file_mark_over (file, FILE_OVER_READ_FROM_PEER);
break;
case HIO_HTTP_DELETE:
if (file->options & HIO_SVC_HTTS_FILE_READ_ONLY)
{
status_code = HIO_HTTP_STATUS_METHOD_NOT_ALLOWED;
goto done_with_status_code;
}
if (unlink(actual_file) <= -1)
{
if (errno != EISDIR || (errno == EISDIR && rmdir(actual_file) <= -1))
{
status_code = ERRNO_TO_STATUS_CODE(errno);
goto done_with_status_code;
}
}
status_code = HIO_HTTP_STATUS_OK;
goto done_with_status_code;
default:
status_code = HIO_HTTP_STATUS_METHOD_NOT_ALLOWED;
done_with_status_code:
if (file_send_final_status_to_client(file, status_code, 0) <= -1) goto oops;
file_mark_over (file, FILE_OVER_READ_FROM_PEER | FILE_OVER_WRITE_TO_PEER);
break;
}
/* TODO: store current input watching state and use it when destroying the file data */