xref: /haiku/src/apps/poorman/PoorManServer.cpp (revision 7749d0bb0c358a3279b1b9cc76d8376e900130a5)
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