/*
* Copyright 2013 Simon Thiel
*
* This file is part of SitJar.
*
* SitJar is free software: you can redistribute it and/or modify
* it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SitJar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SitJar. If not, see <http://www.gnu.org/licenses/lgpl.txt>.
*/
/*
* @author Simon Thiel <simon.thiel at gmx.de>
*/
package sit.web;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.Calendar;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import sit.sstl.ByteBuilder;
import sit.web.client.HttpHelper;
import sit.web.socket.SocketI;
/**
*
* @author thiel
*/
class WebWorker implements HttpConstants, Runnable {
/*
* buffer to use for requests
*/
private WebBuffer buf = new WebBuffer();
private HTTPParser httphelp = new HTTPParser();
/*
* Socket to client we're handling
*/
private SocketI socket = null;
/**
* indicates whether the thread is ordered to stop
*/
private boolean stopping = false;
private final static Logger logger = Logger.getLogger(WebWorker.class.getName());
private final static Level level = logger.getLevel();
private final static String e404Msg="<html><body><h1>Not Found</h1><br/><br/>\n\n"
+ "The requested resource was not found.\n</body></html>";
private final static String e403Msg="<html><body><h1>Forbidden</h1><br/><br/>\n\n"
+ "Access to the requested resource was denied.\n</body></html>";
private final static String e500Msg="<html><body><h1>Internal Server Error</h1><br/><br/>\n\n"
+ "The server had an internal error.\n</body></html>";
public synchronized void setSocket(SocketI s) {
this.socket = s;
notify();
}
public void stop() {
this.stopping = true;
logger.log(Level.FINE, "stop WebWorker");
}
@Override
public void run() {
while (!stopping) {
synchronized (this) {
if (socket == null) {
/*
* nothing to do
*/
try {
wait(); //wait releases the monitor
} catch (InterruptedException e) {
continue;
}
}
try {
handleClient();
} catch (Exception ex) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, ex);
}
socket = null;
}
//try to add this thread to the pool
WebServer.getInstance().addThreadToPool(this);
}
}
private WebRequest getWebRequest(InputStream is, PrintStream ps) throws UnsupportedHTTPMethodException, IOException, MessageTooLargeException, HTTPParseException {
HTTPMessage result = httphelp.getHeaderAndBody(is, buf, ps);
if (result == null) {
return null;
}
return result.getWebRequest();
}
private void handleClient() throws IOException {
InputStream is = new BufferedInputStream(socket.getInputStream());
PrintStream ps = new PrintStream(socket.getOutputStream());
/*
* we will only block in read for this many milliseconds before we fail
* with java.io.InterruptedIOException, at which point we will abandon
* the connection.
*/
socket.setSoTimeout(WebServer.getInstance().getTimeOut());
socket.setTcpNoDelay(true);
try {
WebRequest request = null;
try {
request = getWebRequest(is, ps);
} catch (UnsupportedHTTPMethodException ex) {
String message = "unsupported method type: " + new String(buf.getBytes(0, 5));
sendHtmlMessageAndClose(HTTP_BAD_METHOD, message, ps);
return;
} catch (MessageTooLargeException ex) {
sendHtmlMessageAndClose(HTTP_ENTITY_TOO_LARGE, "Entity Too Large", ps);
logger.log(Level.WARNING, "Entity Too Large:\n" + ex.getMessage());
return;
} catch (HTTPParseException ex) {
sendHtmlMessageAndClose(HTTP_SERVER_ERROR, "Internal Server Error", ps);
logger.log(Level.WARNING, "Internal Server Error:\n" + ex.getMessage());
return;
}
if (request == null) {
socket.close();
return;
}
if (logger.getLevel() == Level.FINE) {
logger.log(Level.FINE, "request:{0}", request.toBriefString());
} else{
logger.log(Level.FINER, "request:{0}", request.toString());
}
//if we find a fitting service call the service
ServiceEndpoint service = ServiceEndpoints.getInstance().getEndpoint(request.fname);
if (service != null) {
logger.log(Level.FINE, "found service:" + service.getEndpointName());
printDynamicPage(service.getContentTypeAsString(), service.getCharSet(), service.handleCall(request), ps);
} else {
handleFileAndDirectoryCall(request, ps);
}
} finally {
socket.close();
}
}
private void handleFileAndDirectoryCall(WebRequest request, PrintStream ps) throws IOException{
String filename=HttpHelper.decodeString(request.fname);
//look for a fitting file/directory
File targetFile = new File(WebServer.getInstance().getRoot(), filename);
//check 404
if (!targetFile.exists()) {
sendHtmlMessageAndClose(HTTP_NOT_FOUND, e404Msg, ps, request.contentType.charSet);
return;
}
//check indexfile
if (targetFile.isDirectory()) {
File indexFile = new File(targetFile, "index.html");
if (indexFile.exists()) {
targetFile = indexFile;
}
}
//check directory listing
if (targetFile.isDirectory()) { //could have been replaced by index.html
//handle directory
if (WebServer.getInstance().isPermitDirectoryListing()) {
String body = getDirectoryList(targetFile);
sendHtmlMessageAndClose(HTTP_OK, body, ps, request.contentType.charSet);
return;
} else {
sendHtmlMessageAndClose(HTTP_FORBIDDEN, e403Msg, ps, request.contentType.charSet);
return;
}
}else{
ByteBuilder headers = getHeaders(HTTP_OK,MimeTypes.getMimeTypeFromFileName(targetFile.getName()),
request.contentType.charSet, targetFile.length());
sendFileAndClose(headers.toByteArray(), targetFile, ps);
return;
}
}
private void printDynamicPage(String contentType, Charset charSet, byte[] content, FilterOutputStream output)
throws IOException {
if (charSet == null) {
charSet = DEFAULT_CHARSET;
}
logger.log(Level.FINE, "content:\n{0}", new String(content, charSet));
output.write(("HTTP/1.0 " + HTTP_OK + " OK").getBytes(charSet));
output.write(CRLF_BYTE);
output.write(("Server: SIT java").getBytes(charSet));
output.write(CRLF_BYTE);
output.write(("Date: " + (new Date())).getBytes(charSet));
output.write(CRLF_BYTE);
output.write((CONTENT_LENGTH_TAG + content.length).getBytes(charSet));
output.write(CRLF_BYTE);
output.write(("Last Modified: " + Calendar.getInstance().getTime()).getBytes(charSet));
output.write(CRLF_BYTE);
output.write((CONTENT_TYPE_TAG + contentType).getBytes(charSet));
output.write(CRLF_BYTE);
output.write(("Connection: close").getBytes(charSet));
output.write(CRLF_BYTE);
output.write(CRLF_BYTE);
output.write(content);
closeCall(output);
}
private ByteBuilder getHeaders(int returnCode, String mimeType, Charset charset, long contentLength){
ByteBuilder result = new ByteBuilder();
String returnCodeMessage = HttpConstantsHelper.getHTTPCodeMessage(returnCode);
if (returnCodeMessage==null){
Logger.getLogger(WebWorker.class.getName()).log(Level.WARNING, "no message found for HTTP code:"+returnCode);
returnCodeMessage="";
}
result.append(("HTTP/1.0 " + returnCode + " "+returnCodeMessage).getBytes(charset));
result.append(HttpConstants.CRLF_BYTE);
result.append("Server: SIT java 0.2".getBytes(charset));
result.append(HttpConstants.CRLF_BYTE);
result.append(("Date: " + (new Date())).getBytes(charset));
result.append(HttpConstants.CRLF_BYTE);
result.append((CONTENT_TYPE_TAG + mimeType
+HttpConstants.SUB_FIELD_SEPARATOR+HttpConstants.CHARSET_CONTENT_TYPE_TAG+charset.name()).getBytes(charset));
result.append(CRLF_BYTE);
result.append((CONTENT_LENGTH_TAG + contentLength).getBytes(charset));
result.append(CRLF_BYTE);
result.append(CRLF_BYTE);
return result;
}
private void closeCall(OutputStream os) throws IOException{
os.flush();
socket.close();
}
private void sendHtmlMessageAndClose(String htmlMessage, PrintStream ps) throws IOException{
sendHtmlMessageAndClose(HTTP_OK, htmlMessage, ps, HttpConstants.DEFAULT_CHARSET);
}
private void sendHtmlMessageAndClose(int returnCode, String htmlMessage, PrintStream ps) throws IOException{
sendHtmlMessageAndClose(returnCode, htmlMessage, ps, HttpConstants.DEFAULT_CHARSET);
}
private void sendHtmlMessageAndClose(int returnCode, String htmlMessage, PrintStream ps, Charset charset) throws IOException{
byte[] message = htmlMessage.getBytes(charset);
ByteBuilder headers = getHeaders(returnCode, DEFAULT_MIME_TYPE, charset, message.length);
sendBytesAndClose(headers.toByteArray(), message, ps);
}
private void sendBytesAndClose(byte[] header, byte[] body, PrintStream ps) throws IOException{
ps.write(header);
ps.write(body);
closeCall(ps);
}
private void sendFileAndClose(byte[] header, File targetFile, PrintStream ps) throws IOException {
ps.write(header);
InputStream is = null;
is = new FileInputStream(targetFile.getAbsolutePath());
try {
int n;
while ((n = buf.readFromInputStream(is)) > 0) {
buf.writeToOutStream(ps, 0, n);
}
} finally {
is.close();
}
closeCall(ps);
}
private String getMyPath(File dir){
String myPath = ServiceEndpointHelper.replaceBackSlashes(
(dir.getPath().length() > 0) ? dir.getPath() : "");
try{//remove previously added prefix from the path
myPath = myPath.substring(WebServer.getInstance().getRoot().getPath().length());
}catch (Exception ex){
Logger.getLogger(WebWorker.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
}
if (!myPath.startsWith("/")){
myPath="/"+myPath;
}
if (myPath.length()==0 || (!myPath.endsWith("/"))){
myPath+="/";
}
return myPath;
}
private String getDirectoryList(File dir) {
String myPath = getMyPath(dir);
StringBuilder result = new StringBuilder();
result.append("<html><head><title>Directory listing</title></head>\n");
result.append("<body><h1>Directory listing</h1>\n");
result.append("<p><a href=\"..\">[Parent directory]</a><br/>\n");
File[] list = dir.listFiles();
for (int i = 0; list != null && i < list.length; i++) {
File f = list[i];
if (f.isDirectory()) {
result.append("<a href=\"" + myPath + f.getName() + "/\">" + f.getName() + "/</a><br/>\n");
} else {
result.append("<a href=\"" + myPath + f.getName() + "\">" + f.getName() + "</a><br/>\n");
}
}
result.append("<br/></p><p><hr></p><p><i>" + (new Date()) + "</i></p></body></html>");
return result.toString();
}
}