package Serfler; // Serfler - an Http server written in Java based on servlets // Copyright (c) 1998 by Douglas Harris // Distributed under the GNU General Public License // // A copy is included with this software distribution. // The package is available from // http://spectral.mscs.mu.edu/javadev/src/serfler.zip // import java.util.Date; import java.text.*; import javax.servlet.*; import javax.servlet.http.*; import java.net.*; import java.io.*; import java.util.*; public class SerflerHandler implements HttpServletRequest,HttpServletResponse,Runnable{ Servlet servlet; Servlet qServlet=null; static SerflerContext loadContext=new SerflerContext(); static ServletContext defaultContext=(ServletContext)loadContext; String SERVLET_PREFIX="/servlet"; SerflerServer queue; String qRequestLine; // the first nonblank line String qMethod; // first token on qRequestLine String qURI; // second token on qRequestLine String qQueryPath; // left ? split of qURI String qServletPath; String qPathInfo; String qPathTranslated; String qQueryString; // right ? split of qURI Hashtable qParameters; String qProtocol; // third token of qRequestLine, else HTTP/0.9 String qScheme; // left / split of qProtocol Hashtable qHeaders; // accumulated until empty line boolean needed=false; int index; Thread t; Socket s; SerflerInputStream i; SerflerOutputStream o; SerflerHandler(SerflerServer queue, int index){ this.queue=queue; this.index=index; rHeaders=new Hashtable(); (t=new Thread(this)).start(); } public int getIndex(){ return index; } //Convenience void log(String msg){ defaultContext.log(msg); } private void parseRequestLine(){ qServlet=null; /* This will take the first line read as qRequestLine and parse qRequestLine as qMethod qURI [qProtocol] */ qProtocol="HTTP/0.9"; try{ qRequestLine=i.readLine(); }catch (Exception x){x.printStackTrace();} log(qRequestLine); StringTokenizer z=new StringTokenizer(qRequestLine); if (z.hasMoreTokens()){ qMethod=z.nextToken(); } if (z.hasMoreTokens()){ qURI=z.nextToken(); //could be null if request is GET, else non-empty string //See the HTTP standard for what this means } //If there are no more the protocol is HTTP/0.9 if (z.hasMoreTokens()){ qProtocol=z.nextToken(); } setScheme(); if (z.hasMoreTokens()){//They are BAD! // Should send an error } } private void setScheme(){ int separator; qScheme=getProtocol(); if (-1!=(separator=qScheme.indexOf("/"))){ qScheme=qScheme.substring(0,separator); } } private void parseQueryURI(){ //Just literally split it in half at the first ? int separator; qQueryPath=qURI; //JDH RFC see the RFC for what to do here, I think * is proper if (qQueryPath==null){ qQueryPath="/"; } //JDH RFC This should not occur, should always start with / if (!qQueryPath.startsWith("/")){ qQueryPath="/"+qQueryPath; } qQueryString=""; if (-1!=(separator=qURI.indexOf("?"))){ qQueryString=qQueryPath.substring(separator+1); qQueryPath=qQueryPath.substring(0,separator); } } /* Name: Value of Header .. > qHeaders Empty line terminates them */ private void setQueryHeaders(){ String qHeaderLine; String name; String value; Object oldValue; int separator; qHeaders=new Hashtable(); try{ while((qHeaderLine=i.readLine()).length()>0){ separator=qHeaderLine.indexOf(": "); if (separator==-1){ //BAD HEADER AND NOT YET OUTPUT JDH }else{ name=qHeaderLine.substring(0,separator).toUpperCase(); value=qHeaderLine.substring(separator+2); oldValue=qHeaders.get(name); if(null!=oldValue){ value=((String)oldValue)+", "+value; } qHeaders.put(name, value); } } }catch(IOException x){x.printStackTrace();} } /* name1=value1&name2=value2 -> qParameters */ private void setQueryParameters(){ qParameters=new Hashtable(); if(qMethod.equals("GET")){ if(qQueryString!=null){ //JDH try{ qParameters=HttpUtils.parseQueryString(qQueryString); } catch(IllegalArgumentException x){ log("Illegal Argument in QueryString: "+qQueryString); } } else qParameters=new Hashtable(); }else if ((qMethod.equals("POST"))&& ((getContentType().toLowerCase()).equals("application/x-www-form-urlencoded"))){ try{ qParameters=HttpUtils.parsePostData(getContentLength(),getInputStream()); } catch(IOException x){} } } private void parseQueryPath(){ qServletPath="/file"; qPathInfo=qQueryPath; if ((qQueryPath.equals(SERVLET_PREFIX)|| qQueryPath.startsWith(SERVLET_PREFIX+"/"))){ parseServletPath(); }else{ qServlet=loadContext.qFileServlet; } } private void parseServletPath(){ int slashIndex; int equalIndex; // qQueryPath is either /servlet or /servlet/something if (qQueryPath.equals(SERVLET_PREFIX)){ // It was /servlet qServletPath="/default"; qPathInfo=""; qServlet=loadContext.qDefaultServlet; return; } qQueryPath=qQueryPath.substring(SERVLET_PREFIX.length()); // It was /servlet/something, and we dropped /servlet // /something is either /name, or /name/remainder // Assume it is /name qServletPath=qQueryPath; qPathInfo=""; if (-1!=(slashIndex=qQueryPath.indexOf("/",1))){ // It is /name/remainder qPathInfo=qQueryPath.substring(slashIndex); // remainder qServletPath=qQueryPath.substring(0,slashIndex); // name } if (-1!=qServletPath.indexOf("=")){ qServletPath="/WLoadServlet"; qPathInfo=qQueryPath; qServlet=loadContext.qLoadServlet; } } synchronized public void startConnection(Socket s){ this.s=s; needed=true; notify(); } synchronized public void run(){ while(!needed){ try{ wait(); }catch(InterruptedException x){x.printStackTrace();} if (needed){ handle(); queue.push(this); needed=false; } } } public void handle() { try{ i=new SerflerInputStream(s.getInputStream()); o=new SerflerOutputStream(this,s.getOutputStream()); parseRequestLine(); parseQueryURI(); setQueryHeaders(); setQueryParameters(); parseQueryPath(); setStatus(SC_OK); setDateHeader("Date",(new Date()).getTime()); setHeader("Server",getServerName()); setHeader("MIME-Version", "1.0"); if (qServlet!=null){ servlet=qServlet; } else { servlet=defaultContext.getServlet(getServletPath().substring(1)); } if (servlet==null){ servlet=loadContext.qUnknownServlet; } if (servlet!=null){ servlet.service(this, this); } o.flush(); o.close(); s.close();//JDH maybe not if it should loop }catch(Exception x){x.printStackTrace();} } //======================================================================== //======================================================================== //======================================================================== // ServletRequest //======================================================================== //======================================================================== //======================================================================== /** * Returns the size of the request entity data, or -1 if not known. * Same as the CGI variable CONTENT_LENGTH. */ public int getContentLength(){ return getIntHeader("Content-Length"); } /** * Returns the Internet Media Type of the request entity data, or * null if not known. Same as the CGI variable CONTENT_TYPE. */ public String getContentType(){ return getHeader("Content-Type"); } /** * Returns the protocol and version of the request as a string of the * form <protocol>/<major version>.<minor version>. * Same as the CGI variable SERVER_PROTOCOL. */ public String getProtocol(){ return qProtocol; } public String getScheme(){ return qScheme; } public String getRealPath(String path){ return defaultContext.getRealPath(path); } /** * Returns the host name of the server that received the request. * Same as the CGI variable SERVER_NAME. */ public String getServerName(){ String h=null; try{ h=InetAddress.getLocalHost().getHostName(); }catch(UnknownHostException x){x.printStackTrace();} return h; } /** * Returns the port number on which this request was received. * Same as the CGI variable SERVER_PORT. */ public int getServerPort(){ return s.getLocalPort(); } /** * Returns the IP address of the agent that sent the request. * Same as the CGI variable REMOTE_ADDR. */ public String getRemoteAddr(){ return s.getInetAddress().toString(); } /** * Returns the fully qualified host name of the agent that sent the * request. Same as the CGI variable REMOTE_HOST. */ public String getRemoteHost(){ return s.getInetAddress().getHostName(); } boolean sInputStreaming=false; boolean sInputReading=false; /** * Returns an input stream for reading the request body. */ public ServletInputStream getInputStream() throws IllegalStateException,IOException{ if (sInputReading){ throw new IllegalStateException(); } sInputStreaming=true; return i; } public BufferedReader getReader() throws UnsupportedEncodingException, IllegalStateException, IOException{ if (sInputStreaming){ throw new IllegalStateException(); } sInputReading=true; return new BufferedReader(new InputStreamReader(i)); } /** * Returns the value of the specified parameter for the request. For * example, in an HTTP servlet this would return the value of the * specified query string parameter. * Deprecated * @param name the parameter name */ public String getParameter(String name){ String val=null; String[] vals=(String[])getParameterValues(name); if (vals==null){ return null; } for (int i=0;i0){ vals[i]=val+", "+vals[i]; } val=vals[i]; } return val; } public String[] getParameterValues(String name){ String[] vals= (String[]) qParameters.get(name); if (vals==null){ return null; } return (String[]) qParameters.get(name); } /** * Returns an enumeration of strings representing the parameter names * for this request. */ public Enumeration getParameterNames(){ // These are the names for the name=value entries in QueryString return qParameters.keys(); } /** * Returns an attribute of the request given the specified key name. * This allows access to request information not already provided by * the other methods in this interface. Key names beginning with * 'COM.sun.*' are reserved. * @param name the attribute name * @return the value of the attribute, or null if not defined */ public Object getAttribute(String name){ // My server will not support attributes return null; } //======================================================================== //======================================================================== //======================================================================== // END ServletRequest //======================================================================== //======================================================================== //======================================================================== // START HttpServletRequest //======================================================================== //======================================================================== //======================================================================== /** * Returns the method with which the request was made. The returned * value can be "GET", "HEAD", "POST", or an extension method. Same * as the CGI variable REQUEST_METHOD. */ public String getMethod(){ //The first token: GET/HEAD/POST return qMethod; } /** * Returns the request URI as a URL object. */ public String getRequestURI(){ //The second token, maybe need to deal with host in 1.1 return qURI; } /** * Returns the part of the request URI that refers to the servlet * being invoked. Analogous to the CGI variable SCRIPT_NAME. */ public String getServletPath(){ return qServletPath; } /** * Returns optional extra path information following the servlet path, * but immediately preceding the query string. Returns null if not * specified. Same as the CGI variable PATH_INFO. */ public String getPathInfo(){ return qPathInfo; } /** * Returns extra path information translated to a real path. Returns * null if no extra path information specified. Same as the CGI variable * PATH_TRANSLATED. */ public String getPathTranslated(){ //protocol://host:port/servletpath/pathinfo?querystring return null; } /** * Returns the query string part of the servlet URI, or null if none. * Same as the CGI variable QUERY_STRING. */ public String getQueryString(){ return qQueryString; } /** * Returns the name of the user making this request, or null if not * known. Same as the CGI variable REMOTE_USER. */ public String getRemoteUser(){ //Does not support this return null; } /** * Returns the authentication scheme of the request, or null if none. * Same as the CGI variable AUTH_TYPE. */ public String getAuthType(){ //Does not support this return null; } /** * Returns the value of a header field, or null if not known. * The case of the header field name is ignored. * @param name the case-insensitive header field name */ public String getHeader(String name){ return (String)qHeaders.get(name.toUpperCase()); } /** * Returns the value of an integer header field, or -1 if not found. * The case of the header field name is ignored. * @param name the case-insensitive header field name */ public int getIntHeader(String name){ String str=(getHeader(name)); if (str !=null){ return (Integer.parseInt(str)); } else{ return -1; } } /** * Returns the value of a date header field, or -1 if not found. * The case of the header field name is ignored. * @param name the case-insensitive header field name */ public long getDateHeader(String name){ SimpleDateFormat fDate=new SimpleDateFormat("EEEE, dd-MMMM-yy hh:mm:ss zzz"); long d=-1; String str=(getHeader(name)); if (str !=null){ try{ d=fDate.parse(str).getTime(); }catch(ParseException x){x.printStackTrace();} } return (d); } /** * Returns an enumeration of strings representing the header names * for this request. Some server implementations do not allow headers * to be accessed in this way, in which case this method will return null. */ public Enumeration getHeaderNames(){ // Read these into a Hashtable from the input return qHeaders.keys(); } public Cookie[] getCookies(){ String head=getHeader("Cookie"); if (head==null){ return new Cookie[0]; } StringTokenizer z=new StringTokenizer(head, ","); Cookie[] cookies=new Cookie[z.countTokens()]; index=0; while (z.hasMoreTokens()){ cookies[index++]=Utilities.HttpCookie.parseHeaderString(z.nextToken()); } return cookies; } public String getRequestedSessionId(){ return null; } public HttpSession getSession(boolean yes){ return null; } public boolean isRequestedSessionIdFromCookie(){ return false; } public boolean isRequestedSessionIdFromUrl(){ return false; } public boolean isRequestedSessionIdValid(){ return false; } //======================================================================== //======================================================================== //======================================================================== // END HttpServletRequest //======================================================================== //======================================================================== //======================================================================== //======================================================================== //======================================================================== //======================================================================== // START ServletResponse //======================================================================== //======================================================================== //======================================================================== String rStatusLine; int rStatusCode; String rStatusMessage; String rProtocol="HTTP/1.0"; Hashtable rHeaders; String rContentType; int rContentLength; /** * Sets the content length for this response. * @param len the content length */ public void setContentLength(int len){ setIntHeader("Content-Length",len); } /** * Sets the content type for this response. * @param type the content type */ public void setContentType(String type){ rContentType=type; setHeader("Content-Type",type); } private boolean outputWriting=false; private boolean outputStreaming=false; /** * Returns an output stream for writing response data. * @exception IOException if an I/O exception has occurred */ public ServletOutputStream getOutputStream() throws IllegalStateException, IOException{ if (outputWriting==true){ throw new IllegalStateException(); } outputStreaming=true; return o; } public PrintWriter getWriter() throws UnsupportedEncodingException, IllegalStateException, IOException{ if (outputStreaming==true){ throw new IllegalStateException(); } outputWriting=true; return new PrintWriter(o); } public String getCharacterEncoding(){ return null; } //======================================================================== //======================================================================== //======================================================================== // END ServletResponse //======================================================================== //======================================================================== //======================================================================== // START HttpServletResponse //======================================================================== //======================================================================== //======================================================================== static Hashtable sStatus; static{ sStatus=new Hashtable(); sStatus.put(Integer.toString(SC_OK),"OK"); sStatus.put(Integer.toString(SC_CREATED), "Created"); sStatus.put(Integer.toString(SC_ACCEPTED), "Accepted"); sStatus.put(Integer.toString(SC_NON_AUTHORITATIVE_INFORMATION), "Non Authoritative"); sStatus.put(Integer.toString(SC_NO_CONTENT), "No Content"); sStatus.put(Integer.toString(SC_RESET_CONTENT), "Reset Content"); sStatus.put(Integer.toString(SC_PARTIAL_CONTENT), "Partial Content"); sStatus.put(Integer.toString(SC_MOVED_PERMANENTLY), "Moved Permanently"); sStatus.put(Integer.toString(SC_MOVED_TEMPORARILY), "Move Temporarily"); sStatus.put(Integer.toString(SC_SEE_OTHER), "See Other"); sStatus.put(Integer.toString(SC_NOT_MODIFIED), "Not Modified"); sStatus.put(Integer.toString(SC_USE_PROXY), "Use Proxy"); sStatus.put(Integer.toString(SC_BAD_REQUEST), "Bad Request"); sStatus.put(Integer.toString(SC_UNAUTHORIZED), "Unauthorized"); sStatus.put(Integer.toString(SC_PAYMENT_REQUIRED), "Payment Required"); sStatus.put(Integer.toString(SC_FORBIDDEN), "Forbidden"); sStatus.put(Integer.toString(SC_NOT_FOUND), "Not Found"); sStatus.put(Integer.toString(SC_METHOD_NOT_ALLOWED), "Method Not Allowed"); sStatus.put(Integer.toString(SC_INTERNAL_SERVER_ERROR), "Internal Server Error"); sStatus.put(Integer.toString(SC_NOT_IMPLEMENTED), "Not Implemented"); sStatus.put(Integer.toString(SC_BAD_GATEWAY), "Bad Gateway"); sStatus.put(Integer.toString(SC_SERVICE_UNAVAILABLE), "Service Unavailable"); } /** * Sets the status code and message for this response. * @param sc the status code * @param sm the status message */ public void setStatus(int sc, String sm){ // Use the codes rStatusCode=sc; rStatusMessage=sm; rStatusLine=rProtocol+" "+rStatusCode+" "+rStatusMessage; } /** * Sets the status code and a default message for this response. * @param sc the status code */ public void setStatus(int sc){ String sm=(String)sStatus.get(Integer.toString(sc)); setStatus(sc,sm); } //JDH Needs Work public boolean containsHeader(String name){ return false; } /** * Sets the value of a header field. * @param name the header field name * @param value the header field value */ public void setHeader(String name, String value){ rHeaders.put(name,value); } /** * Sets the value of an integer header field. * @param name the header field name * @param value the header field integer value */ public void setIntHeader(String name, int value){ setHeader(name, Integer.toString(value)); } /** * Sets the value of a date header field. * @param name the header field name * @param value the header field date value */ public void setDateHeader(String name, long date){ java.text.SimpleDateFormat f= new java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'"); f.setTimeZone(java.util.TimeZone.getTimeZone("GMT")); setHeader(name, f.format(new Date(date))); } /** * Sends an error response to the client using the specified status * code and detail message. * @param sc the status code * @param msg the detail message * @exception IOException If an I/O error has occurred. */ public void sendError(int sc, String msg) throws IOException{ setStatus(sc, msg); } /** * Sends an error response to the client using the specified status * code and no default message. * @param sc the status code * @exception IOException If an I/O error has occurred. */ public void sendError(int sc) throws IOException{ setStatus(sc); setContentType("text/html"); o.println(" Error Message "); o.println("\"Serfler\""); Error Message "); o.println("

Status Code: "+sc+"

"); o.println("

Message: "+rStatusMessage+"

"); o.println(""); o.close(); } /** * Sends a redirect response to the client using the specified redirect * location URL. * @param location the redirect location URL * @exception IOException If an I/O error has occurred. */ public void sendRedirect(String location) throws IOException{ setHeader("Location",location); sendError(SC_MOVED_TEMPORARILY); } //======================================================================== //======================================================================== //======================================================================== // START HttpServletResponse //======================================================================== //======================================================================== //======================================================================== public void addCookie(Cookie c){ setHeader("Set-Cookie",Utilities.HttpCookie.formatHeaderString(c)); } public String encodeUrl(String s){ return s; } public String encodeRedirectUrl(String s){ return s; } //======================================================================== //======================================================================== //======================================================================== // END HttpServletResponse //======================================================================== //======================================================================== //======================================================================== }