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