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