| 1 | package dolda.jsvc.scgi; |
| 2 | |
| 3 | import java.util.logging.*; |
| 4 | import java.io.*; |
| 5 | import java.net.*; |
| 6 | import java.util.*; |
| 7 | |
| 8 | public abstract class Server implements Runnable { |
| 9 | private final ServerSocket sk; |
| 10 | private final Logger logger = Logger.getLogger("dolda.jsvc.scgi"); |
| 11 | private boolean running = false; |
| 12 | public String headcs = "UTF-8"; |
| 13 | |
| 14 | public Server(ServerSocket sk) { |
| 15 | this.sk = sk; |
| 16 | } |
| 17 | |
| 18 | private static int readnslen(InputStream in) throws IOException { |
| 19 | int ret = 0; |
| 20 | while(true) { |
| 21 | int c = in.read(); |
| 22 | if(c == ':') |
| 23 | return(ret); |
| 24 | else if((c >= '0') && (c <= '9')) |
| 25 | ret = (ret * 10) + (c - '0'); |
| 26 | else |
| 27 | throw(new InvalidRequestException("Malformed netstring length")); |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | private static byte[] readns(InputStream in) throws IOException { |
| 32 | byte[] buf = new byte[readnslen(in)]; |
| 33 | int off = 0; |
| 34 | while(off < buf.length) { |
| 35 | int ret = in.read(buf, off, buf.length - off); |
| 36 | if(ret < 0) |
| 37 | throw(new InvalidRequestException("Unexpected EOS in netstring")); |
| 38 | off += ret; |
| 39 | } |
| 40 | if(in.read() != ',') |
| 41 | throw(new InvalidRequestException("Unterminated netstring")); |
| 42 | return(buf); |
| 43 | } |
| 44 | |
| 45 | private Map<String, String> readhead(InputStream in) throws IOException { |
| 46 | byte[] rawhead = readns(in); |
| 47 | String head = new String(rawhead, headcs); |
| 48 | Map<String, String> ret = new HashMap<String, String>(); |
| 49 | int p = 0; |
| 50 | while(true) { |
| 51 | int p2 = head.indexOf(0, p); |
| 52 | if(p2 < 0) { |
| 53 | if(p == head.length()) |
| 54 | return(ret); |
| 55 | throw(new InvalidRequestException("Malformed headers")); |
| 56 | } |
| 57 | String key = head.substring(p, p2); |
| 58 | int p3 = head.indexOf(0, p2 + 1); |
| 59 | if(p3 < 0) |
| 60 | throw(new InvalidRequestException("Malformed headers")); |
| 61 | String val = head.substring(p2 + 1, p3); |
| 62 | ret.put(key, val); |
| 63 | p = p3 + 1; |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | private boolean checkhead(Map<String, String> head) { |
| 68 | if(!head.containsKey("SCGI") || !head.get("SCGI").equals("1")) |
| 69 | return(false); |
| 70 | return(true); |
| 71 | } |
| 72 | |
| 73 | protected abstract void handle(Map<String, String> head, Socket sk) throws Exception; |
| 74 | |
| 75 | private void serve(Socket sk) { |
| 76 | try { |
| 77 | try { |
| 78 | InputStream in = sk.getInputStream(); |
| 79 | Map<String, String> head = readhead(in); |
| 80 | if(!checkhead(head)) |
| 81 | return; |
| 82 | try { |
| 83 | handle(head, sk); |
| 84 | } catch(Exception e) { |
| 85 | logger.log(Level.WARNING, "Could not handle request", e); |
| 86 | return; |
| 87 | } |
| 88 | sk = null; |
| 89 | } finally { |
| 90 | if(sk != null) |
| 91 | sk.close(); |
| 92 | } |
| 93 | } catch(IOException e) { |
| 94 | logger.log(Level.WARNING, "I/O error encountered while serving SCGI request", e); |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | public void run() { |
| 99 | try { |
| 100 | try { |
| 101 | synchronized(this) { |
| 102 | if(running) |
| 103 | throw(new IllegalStateException("SCGI server is already running")); |
| 104 | running = true; |
| 105 | } |
| 106 | while(running) { |
| 107 | Socket nsk = sk.accept(); |
| 108 | serve(nsk); |
| 109 | } |
| 110 | } finally { |
| 111 | sk.close(); |
| 112 | } |
| 113 | } catch(IOException e) { |
| 114 | if((e instanceof SocketException) && !running) { |
| 115 | /* Assume that stop() has closed the socket. */ |
| 116 | } else { |
| 117 | logger.log(Level.SEVERE, "SCGI server encountered I/O error", e); |
| 118 | } |
| 119 | } finally { |
| 120 | shutdown(); |
| 121 | running = false; |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | public void stop() { |
| 126 | try { |
| 127 | running = false; |
| 128 | sk.close(); |
| 129 | } catch(IOException e) { |
| 130 | throw(new RuntimeException(e)); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | protected void shutdown() { |
| 135 | } |
| 136 | } |