1 /* 2 * Copyright 2009 Haiku Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Author(s): 6 * Ma Jie, china.majie at gmail 7 */ 8 9 #include "PoorManServer.h" 10 11 #include <errno.h> 12 #include <pthread.h> 13 #include <string.h> 14 #include <stdlib.h> 15 #include <time.h> //for struct timeval 16 #include <sys/socket.h> 17 #include <netinet/in.h> 18 #include <arpa/inet.h> 19 #include <poll.h> 20 21 #include <File.h> 22 #include <Debug.h> 23 #include <OS.h> 24 #include <String.h> 25 #include <StorageDefs.h> 26 #include <SupportDefs.h> 27 28 #include "PoorManApplication.h" 29 #include "PoorManLogger.h" 30 #include "PoorManWindow.h" 31 #include "libhttpd/libhttpd.h" 32 33 34 PoorManServer::PoorManServer(const char* webDir, 35 int32 maxConns, bool listDir,const char* idxName) 36 :fIsRunning(false), 37 fMaxConns(maxConns), 38 fIndexName(new char[strlen(idxName) + 1]), 39 fCurConns(0) 40 { 41 fHttpdServer = httpd_initialize( 42 (char*)0,//hostname 43 (httpd_sockaddr*)0,//sa4P 44 (httpd_sockaddr*)0,//sa6P 45 (unsigned short)80,//port 46 (char*)0,//cgi pattern 47 0,//cgi_limit 48 (char*)"utf-8",//charset 49 (char *)"",//p3p 50 -1,//max_age 51 const_cast<char*>(webDir),//cwd 52 1,//no_log 53 (FILE*)0,//logfp 54 0,//no_symlink_check 55 0,//vhost 56 0,//global_passwd 57 (char*)0,//url_pattern 58 (char*)0,//local_pattern 59 0//no_empty_referers 60 ); 61 62 strcpy(fIndexName, idxName); 63 64 size_t cwdLen = strlen(fHttpdServer->cwd); 65 if (fHttpdServer->cwd[cwdLen-1] == '/') { 66 fHttpdServer->cwd[cwdLen-1] = '\0'; 67 } 68 69 fHttpdServer->do_list_dir = (listDir ? 1 : 0); 70 fHttpdServer->index_name = fIndexName; 71 72 pthread_rwlock_init(&fWebDirLock, NULL); 73 pthread_rwlock_init(&fIndexNameLock, NULL); 74 } 75 76 77 PoorManServer::~PoorManServer() 78 { 79 Stop(); 80 httpd_terminate(fHttpdServer); 81 delete[] fIndexName; 82 pthread_rwlock_destroy(&fWebDirLock); 83 pthread_rwlock_destroy(&fIndexNameLock); 84 } 85 86 87 status_t PoorManServer::Run() 88 { 89 if (chdir(fHttpdServer->cwd) == -1) { 90 poorman_log("no web directory, can't start up.\n", false, NULL, RED); 91 return B_ERROR; 92 } 93 94 httpd_sockaddr sa4; 95 memset(&sa4, 0, sizeof(httpd_sockaddr)); 96 sa4.sa_in.sin_family = AF_INET; 97 sa4.sa_in.sin_port = htons(80); 98 sa4.sa_in.sin_addr.s_addr = htonl(INADDR_ANY); 99 fHttpdServer->listen4_fd = httpd_initialize_listen_socket(&sa4); 100 101 httpd_sockaddr sa6; 102 memset(&sa6, 0, sizeof(httpd_sockaddr)); 103 sa6.sa_in.sin_family = AF_INET6; 104 sa6.sa_in.sin_port = htons(80); 105 sa6.sa_in.sin_addr.s_addr = htonl(INADDR_ANY); 106 fHttpdServer->listen6_fd = httpd_initialize_listen_socket(&sa6); 107 108 if (fHttpdServer->listen4_fd == -1 && fHttpdServer->listen6_fd == -1) 109 return B_ERROR; 110 111 fListenerTid = spawn_thread( 112 PoorManServer::_Listener, 113 "www listener", 114 B_NORMAL_PRIORITY, 115 static_cast<void*>(this) 116 ); 117 if (fListenerTid < B_OK) { 118 poorman_log("can't create listener thread.\n", false, NULL, RED); 119 return B_ERROR; 120 } 121 fIsRunning = true; 122 if (resume_thread(fListenerTid) != B_OK) { 123 fIsRunning = false; 124 return B_ERROR; 125 } 126 127 //our server is up and running 128 return B_OK; 129 } 130 131 132 status_t PoorManServer::Stop() 133 { 134 if (fIsRunning) { 135 fIsRunning = false; 136 httpd_unlisten(fHttpdServer); 137 } 138 return B_OK; 139 } 140 141 142 /*The Web Dir is not changed if an error occured. 143 */ 144 status_t PoorManServer::SetWebDir(const char* webDir) 145 { 146 if (chdir(webDir) == -1) { 147 //log it 148 return B_ERROR; 149 } 150 151 char* tmp = strdup(webDir); 152 if (tmp == NULL) 153 return B_ERROR; 154 155 if (pthread_rwlock_wrlock(&fWebDirLock) == 0) { 156 free(fHttpdServer->cwd); 157 fHttpdServer->cwd = tmp; 158 if (tmp[strlen(tmp) - 1] == '/') { 159 tmp[strlen(tmp) - 1] = '\0'; 160 } 161 pthread_rwlock_unlock(&fWebDirLock); 162 } else { 163 free(tmp); 164 return B_ERROR; 165 } 166 167 return B_OK; 168 } 169 170 171 status_t PoorManServer::SetMaxConns(int32 count) 172 { 173 fMaxConns = count; 174 return B_OK; 175 } 176 177 178 status_t PoorManServer::SetListDir(bool listDir) 179 { 180 fHttpdServer->do_list_dir = (listDir ? 1 : 0); 181 return B_OK; 182 } 183 184 185 status_t PoorManServer::SetIndexName(const char* idxName) 186 { 187 size_t length = strlen(idxName); 188 if (length > B_PATH_NAME_LENGTH + 1) 189 return B_ERROR; 190 191 char* tmp = new char[length + 1]; 192 if (tmp == NULL) 193 return B_ERROR; 194 195 strcpy(tmp, idxName); 196 if (pthread_rwlock_wrlock(&fIndexNameLock) == 0) { 197 delete[] fIndexName; 198 fIndexName = tmp; 199 fHttpdServer->index_name = fIndexName; 200 pthread_rwlock_unlock(&fIndexNameLock); 201 } else { 202 delete[] tmp; 203 return B_ERROR; 204 } 205 206 return B_OK; 207 } 208 209 210 int32 PoorManServer::_Listener(void* data) 211 { 212 PRINT(("The listener thread is working.\n")); 213 int retval; 214 thread_id tid; 215 httpd_conn* hc; 216 PoorManServer* s = static_cast<PoorManServer*>(data); 217 const int nfds = 2; 218 pollfd fds[nfds]; 219 220 // N.B. these fds could be -1, which poll() should skip 221 memset(&fds, 0, sizeof(fds)); 222 fds[0].fd = s->fHttpdServer->listen4_fd; 223 fds[0].events = POLLIN; 224 fds[1].fd = s->fHttpdServer->listen6_fd; 225 fds[1].events = POLLIN; 226 227 while (s->fIsRunning) { 228 // Wait for listen4_fd or listen6_fd (or both!) to become ready: 229 retval = poll(fds, nfds, -1); 230 if (retval == -1 && errno == EINTR) 231 continue; 232 if (retval < 1) { 233 return -1; // fds no longer available 234 } 235 236 for (int fdi = 0; fdi < nfds; fdi++) { 237 if (fds[fdi].fd < 0) { 238 continue; // fd is disabled, e.g. ipv4-only 239 } 240 if ((fds[fdi].revents & POLLIN) != POLLIN) { 241 continue; // fd is unavailable, try next fd 242 } 243 244 hc = new httpd_conn; 245 hc->initialized = 0; 246 247 PRINT(("calling httpd_get_conn()\n")); 248 retval = httpd_get_conn(s->fHttpdServer, fds[fdi].fd, hc); 249 switch (retval) { 250 case GC_OK: 251 break; 252 case GC_FAIL: 253 httpd_destroy_conn(hc); 254 delete hc; 255 s->fIsRunning = false; 256 return -1; 257 case GC_NO_MORE: 258 //should not happen, since we have a blocking socket 259 httpd_destroy_conn(hc); 260 continue; 261 break; 262 default: 263 //shouldn't happen 264 continue; 265 break; 266 } 267 268 if (s->fCurConns > s->fMaxConns) { 269 httpd_send_err(hc, 503, 270 httpd_err503title, (char *)"", httpd_err503form, (char *)""); 271 httpd_write_response(hc); 272 continue; 273 } 274 275 tid = spawn_thread( 276 PoorManServer::_Worker, 277 "www connection", 278 B_NORMAL_PRIORITY, 279 static_cast<void*>(s) 280 ); 281 if (tid < B_OK) { 282 continue; 283 } 284 /*We don't check the return code here. 285 *As we can't kill a thread that doesn't receive the 286 *httpd_conn, we simply let it die itself. 287 */ 288 send_data(tid, 512, &hc, sizeof(httpd_conn*)); 289 atomic_add(&s->fCurConns, 1); 290 resume_thread(tid); 291 }//for 292 }//while 293 return 0; 294 } 295 296 297 int32 PoorManServer::_Worker(void* data) 298 { 299 static const struct timeval kTimeVal = {60, 0}; 300 PoorManServer* s = static_cast<PoorManServer*>(data); 301 httpd_conn* hc; 302 int retval; 303 304 if (has_data(find_thread(NULL))) { 305 thread_id sender; 306 if (receive_data(&sender, &hc, sizeof(httpd_conn*)) != 512) 307 goto cleanup; 308 } else { 309 // No need to go throught the whole cleanup, as we haven't open 310 // nor allocated ht yet. 311 atomic_add(&s->fCurConns, -1); 312 return 0; 313 } 314 315 PRINT(("A worker thread starts to work.\n")); 316 317 setsockopt(hc->conn_fd, SOL_SOCKET, SO_RCVTIMEO, &kTimeVal, 318 sizeof(struct timeval)); 319 retval = recv( 320 hc->conn_fd, 321 &(hc->read_buf[hc->read_idx]), 322 hc->read_size - hc->read_idx, 323 0 324 ); 325 if (retval < 0) 326 goto cleanup; 327 328 hc->read_idx += retval; 329 switch(httpd_got_request(hc)) { 330 case GR_GOT_REQUEST: 331 break; 332 case GR_BAD_REQUEST: 333 httpd_send_err(hc, 400, 334 httpd_err400title, (char *)"", httpd_err400form, (char *)""); 335 httpd_write_response(hc);//fall through 336 case GR_NO_REQUEST: //fall through 337 default: //won't happen 338 goto cleanup; 339 break; 340 } 341 342 if (httpd_parse_request(hc) < 0) { 343 httpd_write_response(hc); 344 goto cleanup; 345 } 346 347 retval = httpd_start_request(hc, (struct timeval*)0); 348 if (retval < 0) { 349 httpd_write_response(hc); 350 goto cleanup; 351 } 352 353 /*true means the connection is already handled 354 *by the directory index generator in httpd_start_request(). 355 */ 356 if (hc->processed_directory_index == 1) { 357 if (hc->method == METHOD_GET) { 358 static_cast<PoorManApplication*>(be_app)->GetPoorManWindow()->SetHits( 359 static_cast<PoorManApplication*>(be_app)->GetPoorManWindow()->GetHits() + 1 360 ); 361 } 362 goto cleanup; 363 } 364 365 switch (hc->method) { 366 case METHOD_GET: 367 s->_HandleGet(hc); 368 break; 369 case METHOD_HEAD: 370 s->_HandleHead(hc); 371 break; 372 case METHOD_POST: 373 s->_HandlePost(hc); 374 break; 375 } 376 377 cleanup: ; 378 httpd_close_conn(hc, (struct timeval*)0); 379 httpd_destroy_conn(hc); 380 381 delete hc; 382 atomic_add(&s->fCurConns, -1); 383 return 0; 384 } 385 386 387 status_t PoorManServer::_HandleGet(httpd_conn* hc) 388 { 389 PRINT(("HandleGet() called\n")); 390 391 ssize_t bytesRead; 392 uint8* buf; 393 BString log; 394 395 BFile file(hc->expnfilename, B_READ_ONLY); 396 if (file.InitCheck() != B_OK) 397 return B_ERROR; 398 399 buf = new uint8[POOR_MAN_BUF_SIZE]; 400 if (buf == NULL) 401 return B_ERROR; 402 403 static_cast<PoorManApplication*>(be_app)->GetPoorManWindow()->SetHits( 404 static_cast<PoorManApplication*>(be_app)-> 405 GetPoorManWindow()->GetHits() + 1); 406 407 log.SetTo("Sending file: "); 408 if (pthread_rwlock_rdlock(&fWebDirLock) == 0) { 409 log << hc->hs->cwd; 410 pthread_rwlock_unlock(&fWebDirLock); 411 } 412 log << '/' << hc->expnfilename << '\n'; 413 poorman_log(log.String(), true, &hc->client_addr); 414 415 //send mime headers 416 if (send(hc->conn_fd, hc->response, hc->responselen, 0) < 0) { 417 delete [] buf; 418 return B_ERROR; 419 } 420 421 file.Seek(hc->first_byte_index, SEEK_SET); 422 while (true) { 423 bytesRead = file.Read(buf, POOR_MAN_BUF_SIZE); 424 if (bytesRead == 0) 425 break; 426 else if (bytesRead < 0) { 427 delete [] buf; 428 return B_ERROR; 429 } 430 if (send(hc->conn_fd, (void*)buf, bytesRead, 0) < 0) { 431 log.SetTo("Error sending file: "); 432 if (pthread_rwlock_rdlock(&fWebDirLock) == 0) { 433 log << hc->hs->cwd; 434 pthread_rwlock_unlock(&fWebDirLock); 435 } 436 log << '/' << hc->expnfilename << '\n'; 437 poorman_log(log.String(), true, &hc->client_addr, RED); 438 delete [] buf; 439 return B_ERROR; 440 } 441 } 442 443 delete [] buf; 444 return B_OK; 445 } 446 447 448 status_t PoorManServer::_HandleHead(httpd_conn* hc) 449 { 450 int retval = send(hc->conn_fd, hc->response, hc->responselen, 0); 451 if (retval == -1) 452 return B_ERROR; 453 return B_OK; 454 } 455 456 457 status_t PoorManServer::_HandlePost(httpd_conn* hc) 458 { 459 //not implemented 460 return B_OK; 461 } 462 463 464 pthread_rwlock_t* get_web_dir_lock() 465 { 466 return static_cast<PoorManApplication*>(be_app)-> 467 GetPoorManWindow()->GetServer()->GetWebDirLock(); 468 } 469 470 471 pthread_rwlock_t* get_index_name_lock() 472 { 473 return static_cast<PoorManApplication*>(be_app)-> 474 GetPoorManWindow()->GetServer()->GetIndexNameLock(); 475 } 476