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 "iso-8859-1",//charset 47 "",//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, "", httpd_err503form, ""); 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 goto cleanup; 274 } 275 276 PRINT(("A worker thread starts to work.\n")); 277 278 setsockopt(hc->conn_fd, SOL_SOCKET, SO_RCVTIMEO, &kTimeVal, 279 sizeof(struct timeval)); 280 retval = recv( 281 hc->conn_fd, 282 &(hc->read_buf[hc->read_idx]), 283 hc->read_size - hc->read_idx, 284 0 285 ); 286 if (retval < 0) 287 goto cleanup; 288 289 hc->read_idx += retval; 290 switch(httpd_got_request(hc)) { 291 case GR_GOT_REQUEST: 292 break; 293 case GR_BAD_REQUEST: 294 httpd_send_err(hc, 400, 295 httpd_err400title, "", httpd_err400form, ""); 296 httpd_write_response(hc);//fall through 297 case GR_NO_REQUEST: //fall through 298 default: //won't happen 299 goto cleanup; 300 break; 301 } 302 303 if (httpd_parse_request(hc) < 0) { 304 httpd_write_response(hc); 305 goto cleanup; 306 } 307 308 retval = httpd_start_request(hc, (struct timeval*)0); 309 if (retval < 0) { 310 httpd_write_response(hc); 311 goto cleanup; 312 } 313 314 /*true means the connection is already handled 315 *by the directory index generator in httpd_start_request(). 316 */ 317 if (hc->file_address == (char*) 0) { 318 static_cast<PoorManApplication*>(be_app)->GetPoorManWindow()->SetHits( 319 static_cast<PoorManApplication*>(be_app)->GetPoorManWindow()->GetHits() + 1 320 ); 321 hc->conn_fd = -1; 322 goto cleanup; 323 } 324 325 switch (hc->method) { 326 case METHOD_GET: 327 s->_HandleGet(hc); 328 break; 329 case METHOD_HEAD: 330 s->_HandleHead(hc); 331 break; 332 case METHOD_POST: 333 s->_HandlePost(hc); 334 break; 335 } 336 337 cleanup: ; 338 httpd_close_conn(hc, (struct timeval*)0); 339 httpd_destroy_conn(hc); 340 341 delete hc; 342 atomic_add(&s->fCurConns, -1); 343 return 0; 344 } 345 346 347 status_t PoorManServer::_HandleGet(httpd_conn* hc) 348 { 349 PRINT(("HandleGet() called\n")); 350 351 off_t length; 352 ssize_t bytesRead; 353 uint8* buf; 354 BString log; 355 356 BFile file(hc->expnfilename, B_READ_ONLY); 357 if (file.InitCheck() != B_OK) 358 return B_ERROR; 359 360 buf = new uint8[POOR_MAN_BUF_SIZE]; 361 if (buf == NULL) 362 return B_ERROR; 363 364 if (hc->got_range == 1) 365 length = hc->last_byte_index + 1 - hc->first_byte_index; 366 else 367 length = hc->sb.st_size; 368 369 static_cast<PoorManApplication*>(be_app)->GetPoorManWindow()->SetHits( 370 static_cast<PoorManApplication*>(be_app)-> 371 GetPoorManWindow()->GetHits() + 1); 372 373 log.SetTo("Sending file: "); 374 if (pthread_rwlock_rdlock(&fWebDirLock) == 0) { 375 log << hc->hs->cwd; 376 pthread_rwlock_unlock(&fWebDirLock); 377 } 378 log << '/' << hc->expnfilename << '\n'; 379 poorman_log(log.String(), true, hc->client_addr.sa_in.sin_addr.s_addr); 380 381 //send mime headers 382 if (send(hc->conn_fd, hc->response, hc->responselen, 0) < 0) { 383 delete [] buf; 384 return B_ERROR; 385 } 386 387 file.Seek(hc->first_byte_index, SEEK_SET); 388 while (true) { 389 bytesRead = file.Read(buf, POOR_MAN_BUF_SIZE); 390 if (bytesRead == 0) 391 break; 392 else if (bytesRead < 0) { 393 delete [] buf; 394 return B_ERROR; 395 } 396 if (send(hc->conn_fd, (void*)buf, bytesRead, 0) < 0) { 397 log.SetTo("Error sending file: "); 398 if (pthread_rwlock_rdlock(&fWebDirLock) == 0) { 399 log << hc->hs->cwd; 400 pthread_rwlock_unlock(&fWebDirLock); 401 } 402 log << '/' << hc->expnfilename << '\n'; 403 poorman_log(log.String(), true, hc->client_addr.sa_in.sin_addr.s_addr, RED); 404 delete [] buf; 405 return B_ERROR; 406 } 407 } 408 409 delete [] buf; 410 return B_OK; 411 } 412 413 414 status_t PoorManServer::_HandleHead(httpd_conn* hc) 415 { 416 int retval = send(hc->conn_fd, hc->response, hc->responselen, 0); 417 if (retval == -1) 418 return B_ERROR; 419 return B_OK; 420 } 421 422 423 status_t PoorManServer::_HandlePost(httpd_conn* hc) 424 { 425 //not implemented 426 return B_OK; 427 } 428 429 430 pthread_rwlock_t* get_web_dir_lock() 431 { 432 return static_cast<PoorManApplication*>(be_app)-> 433 GetPoorManWindow()->GetServer()->GetWebDirLock(); 434 } 435 436 437 pthread_rwlock_t* get_index_name_lock() 438 { 439 return static_cast<PoorManApplication*>(be_app)-> 440 GetPoorManWindow()->GetServer()->GetIndexNameLock(); 441 } 442