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