/* * 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 #include #include #include #include //for struct timeval #include #include #include #include #include #include #include #include #include #include #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(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(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(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(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(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(be_app)->GetPoorManWindow()->SetHits( static_cast(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(be_app)->GetPoorManWindow()->SetHits( static_cast(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(be_app)-> GetPoorManWindow()->GetServer()->GetWebDirLock(); } pthread_rwlock_t* get_index_name_lock() { return static_cast(be_app)-> GetPoorManWindow()->GetServer()->GetIndexNameLock(); }