haiku/src/apps/poorman/PoorManServer.cpp

476 lines
10 KiB
C++

/*
* Copyright 2009 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Author(s):
* Ma Jie, china.majie at gmail
*/
#include "PoorManServer.h"
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <time.h> //for struct timeval
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <File.h>
#include <Debug.h>
#include <OS.h>
#include <String.h>
#include <StorageDefs.h>
#include <SupportDefs.h>
#include "PoorManApplication.h"
#include "PoorManLogger.h"
#include "PoorManWindow.h"
#include "libhttpd/libhttpd.h"
PoorManServer::PoorManServer(const char* webDir,
int32 maxConns, bool listDir,const char* idxName)
:fIsRunning(false),
fMaxConns(maxConns),
fIndexName(new char[strlen(idxName) + 1]),
fCurConns(0)
{
fHttpdServer = httpd_initialize(
(char*)0,//hostname
(httpd_sockaddr*)0,//sa4P
(httpd_sockaddr*)0,//sa6P
(unsigned short)80,//port
(char*)0,//cgi pattern
0,//cgi_limit
(char *)"iso-8859-1",//charset
(char *)"",//p3p
-1,//max_age
const_cast<char*>(webDir),//cwd
1,//no_log
(FILE*)0,//logfp
0,//no_symlink_check
0,//vhost
0,//global_passwd
(char*)0,//url_pattern
(char*)0,//local_pattern
0//no_empty_referers
);
strcpy(fIndexName, idxName);
size_t cwdLen = strlen(fHttpdServer->cwd);
if (fHttpdServer->cwd[cwdLen-1] == '/') {
fHttpdServer->cwd[cwdLen-1] = '\0';
}
fHttpdServer->do_list_dir = (listDir ? 1 : 0);
fHttpdServer->index_name = fIndexName;
pthread_rwlock_init(&fWebDirLock, NULL);
pthread_rwlock_init(&fIndexNameLock, NULL);
}
PoorManServer::~PoorManServer()
{
Stop();
httpd_terminate(fHttpdServer);
delete[] fIndexName;
pthread_rwlock_destroy(&fWebDirLock);
pthread_rwlock_destroy(&fIndexNameLock);
}
status_t PoorManServer::Run()
{
if (chdir(fHttpdServer->cwd) == -1) {
poorman_log("no web directory, can't start up.\n", false, NULL, RED);
return B_ERROR;
}
httpd_sockaddr sa4;
memset(&sa4, 0, sizeof(httpd_sockaddr));
sa4.sa_in.sin_family = AF_INET;
sa4.sa_in.sin_port = htons(80);
sa4.sa_in.sin_addr.s_addr = htonl(INADDR_ANY);
fHttpdServer->listen4_fd = httpd_initialize_listen_socket(&sa4);
httpd_sockaddr sa6;
memset(&sa6, 0, sizeof(httpd_sockaddr));
sa6.sa_in.sin_family = AF_INET6;
sa6.sa_in.sin_port = htons(80);
sa6.sa_in.sin_addr.s_addr = htonl(INADDR_ANY);
fHttpdServer->listen6_fd = httpd_initialize_listen_socket(&sa6);
if (fHttpdServer->listen4_fd == -1 && fHttpdServer->listen6_fd == -1)
return B_ERROR;
fListenerTid = spawn_thread(
PoorManServer::_Listener,
"www listener",
B_NORMAL_PRIORITY,
static_cast<void*>(this)
);
if (fListenerTid < B_OK) {
poorman_log("can't create listener thread.\n", false, NULL, RED);
return B_ERROR;
}
fIsRunning = true;
if (resume_thread(fListenerTid) != B_OK) {
fIsRunning = false;
return B_ERROR;
}
//our server is up and running
return B_OK;
}
status_t PoorManServer::Stop()
{
if (fIsRunning) {
fIsRunning = false;
httpd_unlisten(fHttpdServer);
}
return B_OK;
}
/*The Web Dir is not changed if an error occured.
*/
status_t PoorManServer::SetWebDir(const char* webDir)
{
if (chdir(webDir) == -1) {
//log it
return B_ERROR;
}
char* tmp = strdup(webDir);
if (tmp == NULL)
return B_ERROR;
if (pthread_rwlock_wrlock(&fWebDirLock) == 0) {
free(fHttpdServer->cwd);
fHttpdServer->cwd = tmp;
if (tmp[strlen(tmp) - 1] == '/') {
tmp[strlen(tmp) - 1] = '\0';
}
pthread_rwlock_unlock(&fWebDirLock);
} else {
free(tmp);
return B_ERROR;
}
return B_OK;
}
status_t PoorManServer::SetMaxConns(int32 count)
{
fMaxConns = count;
return B_OK;
}
status_t PoorManServer::SetListDir(bool listDir)
{
fHttpdServer->do_list_dir = (listDir ? 1 : 0);
return B_OK;
}
status_t PoorManServer::SetIndexName(const char* idxName)
{
size_t length = strlen(idxName);
if (length > B_PATH_NAME_LENGTH + 1)
return B_ERROR;
char* tmp = new char[length + 1];
if (tmp == NULL)
return B_ERROR;
strcpy(tmp, idxName);
if (pthread_rwlock_wrlock(&fIndexNameLock) == 0) {
delete[] fIndexName;
fIndexName = tmp;
fHttpdServer->index_name = fIndexName;
pthread_rwlock_unlock(&fIndexNameLock);
} else {
delete[] tmp;
return B_ERROR;
}
return B_OK;
}
int32 PoorManServer::_Listener(void* data)
{
PRINT(("The listener thread is working.\n"));
int retval;
thread_id tid;
httpd_conn* hc;
PoorManServer* s = static_cast<PoorManServer*>(data);
const int nfds = 2;
pollfd fds[nfds];
// N.B. these fds could be -1, which poll() should skip
memset(&fds, 0, sizeof(fds));
fds[0].fd = s->fHttpdServer->listen4_fd;
fds[0].events = POLLIN;
fds[1].fd = s->fHttpdServer->listen6_fd;
fds[1].events = POLLIN;
while (s->fIsRunning) {
// Wait for listen4_fd or listen6_fd (or both!) to become ready:
retval = poll(fds, nfds, -1);
if (retval == -1 && errno == EINTR)
continue;
if (retval < 1) {
return -1; // fds no longer available
}
for (int fdi = 0; fdi < nfds; fdi++) {
if (fds[fdi].fd < 0) {
continue; // fd is disabled, e.g. ipv4-only
}
if ((fds[fdi].revents & POLLIN) != POLLIN) {
continue; // fd is unavailable, try next fd
}
hc = new httpd_conn;
hc->initialized = 0;
PRINT(("calling httpd_get_conn()\n"));
retval = httpd_get_conn(s->fHttpdServer, fds[fdi].fd, hc);
switch (retval) {
case GC_OK:
break;
case GC_FAIL:
httpd_destroy_conn(hc);
delete hc;
s->fIsRunning = false;
return -1;
case GC_NO_MORE:
//should not happen, since we have a blocking socket
httpd_destroy_conn(hc);
continue;
break;
default:
//shouldn't happen
continue;
break;
}
if (s->fCurConns > s->fMaxConns) {
httpd_send_err(hc, 503,
httpd_err503title, (char *)"", httpd_err503form, (char *)"");
httpd_write_response(hc);
continue;
}
tid = spawn_thread(
PoorManServer::_Worker,
"www connection",
B_NORMAL_PRIORITY,
static_cast<void*>(s)
);
if (tid < B_OK) {
continue;
}
/*We don't check the return code here.
*As we can't kill a thread that doesn't receive the
*httpd_conn, we simply let it die itself.
*/
send_data(tid, 512, &hc, sizeof(httpd_conn*));
atomic_add(&s->fCurConns, 1);
resume_thread(tid);
}//for
}//while
return 0;
}
int32 PoorManServer::_Worker(void* data)
{
static const struct timeval kTimeVal = {60, 0};
PoorManServer* s = static_cast<PoorManServer*>(data);
httpd_conn* hc;
int retval;
if (has_data(find_thread(NULL))) {
thread_id sender;
if (receive_data(&sender, &hc, sizeof(httpd_conn*)) != 512)
goto cleanup;
} else {
// No need to go throught the whole cleanup, as we haven't open
// nor allocated ht yet.
atomic_add(&s->fCurConns, -1);
return 0;
}
PRINT(("A worker thread starts to work.\n"));
setsockopt(hc->conn_fd, SOL_SOCKET, SO_RCVTIMEO, &kTimeVal,
sizeof(struct timeval));
retval = recv(
hc->conn_fd,
&(hc->read_buf[hc->read_idx]),
hc->read_size - hc->read_idx,
0
);
if (retval < 0)
goto cleanup;
hc->read_idx += retval;
switch(httpd_got_request(hc)) {
case GR_GOT_REQUEST:
break;
case GR_BAD_REQUEST:
httpd_send_err(hc, 400,
httpd_err400title, (char *)"", httpd_err400form, (char *)"");
httpd_write_response(hc);//fall through
case GR_NO_REQUEST: //fall through
default: //won't happen
goto cleanup;
break;
}
if (httpd_parse_request(hc) < 0) {
httpd_write_response(hc);
goto cleanup;
}
retval = httpd_start_request(hc, (struct timeval*)0);
if (retval < 0) {
httpd_write_response(hc);
goto cleanup;
}
/*true means the connection is already handled
*by the directory index generator in httpd_start_request().
*/
if (hc->processed_directory_index == 1) {
if (hc->method == METHOD_GET) {
static_cast<PoorManApplication*>(be_app)->GetPoorManWindow()->SetHits(
static_cast<PoorManApplication*>(be_app)->GetPoorManWindow()->GetHits() + 1
);
}
goto cleanup;
}
switch (hc->method) {
case METHOD_GET:
s->_HandleGet(hc);
break;
case METHOD_HEAD:
s->_HandleHead(hc);
break;
case METHOD_POST:
s->_HandlePost(hc);
break;
}
cleanup: ;
httpd_close_conn(hc, (struct timeval*)0);
httpd_destroy_conn(hc);
delete hc;
atomic_add(&s->fCurConns, -1);
return 0;
}
status_t PoorManServer::_HandleGet(httpd_conn* hc)
{
PRINT(("HandleGet() called\n"));
ssize_t bytesRead;
uint8* buf;
BString log;
BFile file(hc->expnfilename, B_READ_ONLY);
if (file.InitCheck() != B_OK)
return B_ERROR;
buf = new uint8[POOR_MAN_BUF_SIZE];
if (buf == NULL)
return B_ERROR;
static_cast<PoorManApplication*>(be_app)->GetPoorManWindow()->SetHits(
static_cast<PoorManApplication*>(be_app)->
GetPoorManWindow()->GetHits() + 1);
log.SetTo("Sending file: ");
if (pthread_rwlock_rdlock(&fWebDirLock) == 0) {
log << hc->hs->cwd;
pthread_rwlock_unlock(&fWebDirLock);
}
log << '/' << hc->expnfilename << '\n';
poorman_log(log.String(), true, &hc->client_addr);
//send mime headers
if (send(hc->conn_fd, hc->response, hc->responselen, 0) < 0) {
delete [] buf;
return B_ERROR;
}
file.Seek(hc->first_byte_index, SEEK_SET);
while (true) {
bytesRead = file.Read(buf, POOR_MAN_BUF_SIZE);
if (bytesRead == 0)
break;
else if (bytesRead < 0) {
delete [] buf;
return B_ERROR;
}
if (send(hc->conn_fd, (void*)buf, bytesRead, 0) < 0) {
log.SetTo("Error sending file: ");
if (pthread_rwlock_rdlock(&fWebDirLock) == 0) {
log << hc->hs->cwd;
pthread_rwlock_unlock(&fWebDirLock);
}
log << '/' << hc->expnfilename << '\n';
poorman_log(log.String(), true, &hc->client_addr, RED);
delete [] buf;
return B_ERROR;
}
}
delete [] buf;
return B_OK;
}
status_t PoorManServer::_HandleHead(httpd_conn* hc)
{
int retval = send(hc->conn_fd, hc->response, hc->responselen, 0);
if (retval == -1)
return B_ERROR;
return B_OK;
}
status_t PoorManServer::_HandlePost(httpd_conn* hc)
{
//not implemented
return B_OK;
}
pthread_rwlock_t* get_web_dir_lock()
{
return static_cast<PoorManApplication*>(be_app)->
GetPoorManWindow()->GetServer()->GetWebDirLock();
}
pthread_rwlock_t* get_index_name_lock()
{
return static_cast<PoorManApplication*>(be_app)->
GetPoorManWindow()->GetServer()->GetIndexNameLock();
}