view src/goodjava/webserver/Connection.java @ 1794:04ac5f2771c8

minor
author Franklin Schmidt <fschmidt@gmail.com>
date Sat, 30 Dec 2023 18:41:35 -0700
parents 6c01d54edcac
children 32e77b071e09
line wrap: on
line source

package goodjava.webserver;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.Socket;
import goodjava.logging.Logger;
import goodjava.logging.LoggerFactory;
import goodjava.parser.ParseException;
import goodjava.io.IoUtils;


final class Connection {
	private static final Logger logger = LoggerFactory.getLogger(Connection.class);

	static void handle(Server server,Socket socket) {
		new Connection(server,socket).handle();
	}

	private final Server server;
	private final Socket socket;

	private Connection(Server server,Socket socket) {
		this.server = server;
		this.socket = socket;
	}

	private void handle() {
		String rawHead = "";
		try {
			Request request = new Request();
			Response response;
			String contentType = null;
			try {
				InputStream in = socket.getInputStream();
				byte[] a = new byte[8192];
				int endOfHeader;
				int size = 0;
				int left = a.length;
				outer: while(true) {
					int n = in.read(a,size,left);
					if( n == -1 ) {
						if( size == 0 ) {
							socket.close();
							return;
						}
						throw new IOException("unexpected end of input at "+size);
					}
					size += n;
					for( int i=0; i<=size-4; i++ ) {
						if( a[i]=='\r' && a[i+1]=='\n' && a[i+2]=='\r' && a[i+3]=='\n' ) {
							endOfHeader = i + 4;
							break outer;
						}
					}
					left -= n;
					if( left == 0 ) {
						byte[] a2 = new byte[2*a.length];
						System.arraycopy(a,0,a2,0,size);
						a = a2;
						left = a.length - size;
					}
				}
				rawHead = new String(a,0,endOfHeader);
				//System.out.println(rawHead);
				request.rawHead = rawHead;
				RequestParser parser = new RequestParser(request);
				parser.parseHead();
	
				String lenStr = (String)request.headers.get("content-length");
				if( lenStr != null ) {
					int len = Integer.parseInt(lenStr);
					byte[] body = new byte[len];
					size -= endOfHeader;
					try {
						System.arraycopy(a,endOfHeader,body,0,size);
					} catch(ArrayIndexOutOfBoundsException e) {
						throw new WrappedRuntimeException(e);
					}
					while( size < len ) {
						int n = in.read(body,size,len-size);
						if( n == -1 ) {
							throw new IOException("unexpected end of input at "+size);
						}
						size += n;
					}
					request.body = body;
					//System.out.println(new String(request.body));
				}

				contentType = (String)request.headers.get("content-type");
				if( contentType != null ) {
					contentType = contentType.toLowerCase();
					if( contentType.equals("application/x-www-form-urlencoded") ) {
						parser.parseUrlencoded(null);
					} else if( contentType.equals("application/x-www-form-urlencoded; charset=utf-8") ) {
						parser.parseUrlencoded("utf-8");
					} else if( contentType.startsWith("multipart/form-data;") ) {
						parser.parseMultipart();
					} else if( contentType.equals("application/json") ) {
						parser.parseJson(null);
					} else if( contentType.equals("application/json; charset=utf-8") ) {
						parser.parseJson("utf-8");
					} else {
						logger.info("unknown request content-type: "+contentType);
					}
				}

				String scheme = (String)request.headers.get("x-forwarded-proto");
				if( scheme != null )
					request.scheme = scheme;

				if( "text/event-stream".equals(request.headers.get("accept")) ) {
					ServerSentEvents.add(socket,request);
					return;
				}

				response = server.handler.handle(request);
			} catch(ParseException e) {
				logger.info("parse error\n"+rawHead.trim()+"\n",e);
				String msg = e.toString();
				if( contentType != null )
					msg = "invalid content for content-type " + contentType + "\n" + msg;
				response = Response.errorResponse(Status.BAD_REQUEST,msg);
			}
			response.headers.put("Connection","close");
			response.headers.put("Content-Length",Long.toString(response.body.length));
			byte[] header = response.toHeaderString().getBytes();
	
			OutputStream out = socket.getOutputStream();
			out.write(header);
			IoUtils.copyAll(response.body.content,out);
			out.close();
			socket.close();
		} catch(IOException e) {
			logger.info(rawHead.trim()+"\n",e);
		} catch(WrappedRuntimeException wrapped) {
			RuntimeException e = (RuntimeException)wrapped.getCause();
			logger.warn(rawHead.trim()+"\n",e);
			throw e;
		} catch(RuntimeException e) {
			logger.error(rawHead.trim()+"\n",e);
			throw e;
		}
	}

	private static final class WrappedRuntimeException extends Exception {
		WrappedRuntimeException(RuntimeException e) {
			super(e);
		}
	}
}