io.soluble.pjb.bridge.http.HttpServer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of php-java-bridge Show documentation
Show all versions of php-java-bridge Show documentation
PHPJavaBridge server (soluble fork)
/*-*- mode: Java; tab-width:8 -*-*/
package io.soluble.pjb.bridge.http;
/*
* Copyright (C) 2003-2007 Jost Boekemeier
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import io.soluble.pjb.bridge.ISocketFactory;
import io.soluble.pjb.bridge.AppThreadPool;
import io.soluble.pjb.bridge.Util;
/**
* This class can be used to create a simple HTTP server. It is used
* when running local scripts like e.eval(new
* StringReader('<?php phpinfo(); ?>'));
. For remote
* scripts use a HttpProxy and URLReader instead.
*
* @author jostb
* @see io.soluble.pjb.bridge.http.HttpRequest
* @see io.soluble.pjb.bridge.http.HttpResponse
* @see io.soluble.pjb.script.Continuation
* @see io.soluble.pjb.script.URLReader
* @see io.soluble.pjb.script.Continuation
*/
public abstract class HttpServer implements Runnable {
/**
* Request method GET
*/
public static final String PUT = "PUT";
/**
* Request method PUT
*/
public static final String GET = "GET";
/**
* Request method POST
*/
public static final String POST = "POST";
protected ISocketFactory socket;
protected Thread httpServer;
private AppThreadPool pool;
protected boolean isSecure;
/**
* Create a server socket.
*
* @param addr The host address, either INET:port or INET_LOCAL:port
* @return The server socket.
* @throws IOException
*/
public abstract ISocketFactory bind(String addr) throws IOException;
/**
* Create a server socket.
*
* @param addr The host address, either INET:port or INET_LOCAL:port
* @return The server socket.
* @throws IOException
*/
public abstract ISocketFactory bindSecure(String addr) throws IOException;
/**
* Create a new HTTP Server.
*
* @throws IOException
* @see HttpServer#destroy()
*/
protected HttpServer() throws IOException {
this(null);
}
/**
* Create a new HTTP Server.
*
* @param serverPort The port# as a string. Prefix may be INET: or INET_LOCAL:
* @param isSecure use https instead of http
* @throws IOException
* @see HttpServer#destroy()
*/
protected HttpServer(String serverPort, boolean isSecure) throws IOException {
this.isSecure = isSecure;
if (serverPort == null) serverPort = "0";
if (!serverPort.startsWith("INET"))
serverPort = (Util.JAVABRIDGE_PROMISCUOUS ? "INET:" : "INET_LOCAL:") + serverPort;
socket = isSecure ? bindSecure(serverPort) : bind(serverPort);
try {
pool = createThreadPool("JavaBridgeHttpServerThreadPool");
} catch (SecurityException e) {/*ignore*/}
httpServer = new Util.Thread(this, "JavaBridgeHttpServer");
httpServer.start();
}
/**
* Create a new HTTP Server.
*
* @param serverPort The port# as a string. Prefix may be INET: or INET_LOCAL:
* @throws IOException
* @see HttpServer#destroy()
*/
protected HttpServer(String serverPort) throws IOException {
this(serverPort, false);
}
/**
* Same as Util.createThreadPool, but does not log anything at this stage.
*
* @param name The name of the pool
* @return The thread pool instance.
*/
private AppThreadPool createThreadPool(String name) {
AppThreadPool pool = null;
int maxSize = 20;
try {
maxSize = Integer.parseInt(Util.THREAD_POOL_MAX_SIZE);
} catch (Throwable t) {/*ignore*/}
if (maxSize > 0) pool = new AppThreadPool(name, maxSize);
return pool;
}
/**
* Parse the header. After that req
contains the body.
*
* @param req The HttpRequest
* @return true if the header has been parsed successfully, false otherwise
* @throws UnsupportedEncodingException
* @throws IOException
*/
protected boolean parseHeader(HttpRequest req) throws UnsupportedEncodingException, IOException {
byte buf[] = new byte[Util.BUF_SIZE];
InputStream natIn = req.getInputStream();
int i = 0, n, s = 0;
boolean eoh = false;
boolean rn = false;
String remain = null;
String line;
// the header and content
while ((n = natIn.read(buf, i, buf.length - i)) != -1) {
int N = i + n;
// header
while (!eoh && i < N) {
switch (buf[i++]) {
case '\n':
if (rn) {
eoh = true;
} else {
if (remain != null) {
line = remain + new String(buf, s, i - s, Util.ASCII);
line = line.substring(0, line.length() - 2);
remain = null;
} else {
line = new String(buf, s, i - s - 2, Util.ASCII);
}
req.addHeader(line);
s = i;
}
rn = true;
break;
case '\r':
break;
default:
rn = false;
}
}
// body
if (eoh) {
req.pushBack(buf, i, N - i);
return true;
} else {
if (remain != null) {
remain += new String(buf, s, i - s, Util.ASCII);
} else {
remain = new String(buf, s, i - s, Util.ASCII);
}
s = i = 0;
}
}
return false;
}
private class Runner implements Runnable {
private Socket sock;
private HttpRequest req;
private HttpResponse res;
public Runner(Socket sock) throws IOException {
this.sock = sock;
this.req = new HttpRequest(sock.getInputStream());
this.res = new HttpResponse(sock.getOutputStream());
}
public void run() {
try {
if (parseHeader(req)) service(req, res);
} catch (IOException e) {
Util.printStackTrace(e);
} finally {
try {
this.req.close();
} catch (IOException e) {/*ignore*/}
try {
this.res.close();
} catch (IOException e) {/*ignore*/}
try {
this.sock.close();
} catch (IOException e) {/*ignore*/}
}
}
}
/**
* accept, create a HTTP request and response, parse the header and body
*
* @throws IOException
*/
protected void doRun() throws IOException {
while (true) {
Socket sock;
try {
sock = socket.accept();
} catch (java.net.SocketException e) {
return; // socket closed
} catch (IOException e) {
Util.printStackTrace(e);
return;
}
Util.logDebug("Socket connection accepted");
if (pool == null) {
Util.logDebug("Starting new HTTP server thread");
(new Util.Thread(new Runner(sock), Util.EXTENSION_NAME + "HttpServerRunner")).start();
} else {
Util.logDebug("Starting HTTP server thread from thread pool");
pool.start(new Runner(sock));
}
}
}
protected static final byte[] ERROR_UNAVAIL = Util.toBytes(
"" +
"" +
"503 Service Unavailable " +
"" +
"Out of system resources
" +
"Try again shortly or use the Apache or IIS front end instead.
" +
"
" +
"");
protected void writeServiceUnavailable(HttpRequest req, HttpResponse res) throws IOException {
res.setContentLength(ERROR_UNAVAIL.length);
OutputStream out = res.getOutputStream();
out.write(ERROR_UNAVAIL);
}
protected static final byte[] ERROR = Util.toBytes(
"" +
"" +
"404 Not Found " +
"" +
"Not Found
" +
"The requested URL was not found on this server.
" +
"
" +
"");
protected void writeError(HttpRequest req, HttpResponse res) throws IOException {
res.setContentLength(ERROR.length);
OutputStream out = res.getOutputStream();
out.write(ERROR);
}
protected void doPost(HttpRequest req, HttpResponse res) throws IOException {
writeError(req, res);
}
protected void doGet(HttpRequest req, HttpResponse res) throws IOException {
writeError(req, res);
}
protected void doPut(HttpRequest req, HttpResponse res) throws IOException {
writeError(req, res);
}
/**
* Sets the content length but leaves the rest of the body untouched.
* @param req
* @param res
* @throws java.io.IOException
*/
protected void service(HttpRequest req, HttpResponse res) throws IOException {
String contentLength = req.getHeader("Content-Length");
if (contentLength == null) req.setContentLength(-1);
else req.setContentLength(Integer.parseInt(contentLength));
String method = req.getMethod();
if (null != method) switch (method) {
case PUT:
doPut(req, res);
break;
case GET:
doGet(req, res);
break;
case POST:
doPost(req, res);
break;
default:
break;
}
}
/**
* {@inheritDoc}
*/
@Override
public void run() {
try {
doRun();
} catch (IOException e) {
Util.printStackTrace(e);
}
}
/**
* Stop the HTTP server.
*/
public void destroy() {
try {
socket.close();
} catch (Exception e) {
Util.printStackTrace(e);
}
try {
if (pool != null) pool.destroy();
} catch (Exception e) {
Util.printStackTrace(e);
}
}
/**
* Returns the server socket.
*
* @return The server socket.
*/
public ISocketFactory getSocket() {
return socket;
}
}