/*
ESXX - The friendly ECMAscript/XML Application Server
Copyright (C) 2007-2015 Martin Blom <martin@blom.org>
This program is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.esxx.request;
import java.io.*;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.logging.Level;
import org.bumblescript.jfast.*;
import org.esxx.*;
import org.esxx.util.IO;
import org.esxx.util.StringUtil;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextAction;
public class FCGIRequest
extends WebRequest {
public FCGIRequest(JFastRequest jfast) {
// NOTE: We MUST NOT send the error stream to jfast.error, because
// that stream will, at least by lighttpd, be inserted in the
// output stream, leading to broken HTTP responses!
super(new ByteArrayInputStream(jfast.data), System.err);
jFast = jfast;
}
public void initRequest(URI fs_root_uri)
throws URISyntaxException {
String request_method = jFast.properties.getProperty("REQUEST_METHOD");
URI request_uri;
URI path_translated;
String scheme = jFast.properties.getProperty("HTTPS", "off").equals("on") ? "https" : "http";
String hostname = jFast.properties.getProperty("HTTP_HOST", "localhost");
String path = jFast.properties.getProperty("REQUEST_URI");
String query = jFast.properties.getProperty("QUERY_STRING", "");
if (path != null) {
int q = path.indexOf('?');
if (q != -1) {
path = path.substring(0, q); // Nuke, query string, if present
}
}
else {
// Fall back to PATH_INFO (it might work too)
path = StringUtil.encodeURI(jFast.properties.getProperty("PATH_INFO", ""), false);
}
request_uri = new URI(scheme + "://" + StringUtil.encodeURI(hostname, true)
+ path + (query.isEmpty() ? "" : "?" + query));
if (fs_root_uri == null) {
String pt_path = null;
if (ESXX.getInstance().isHandlerMode(jFast.properties.getProperty("SERVER_SOFTWARE"))) {
pt_path = jFast.properties.getProperty("PATH_TRANSLATED");
}
if (pt_path == null) {
// If not handler mode, or PATH_TRANSLATED missing, use
// SCRIPT_FILENAME + PATH_INFO instead
pt_path = (jFast.properties.getProperty("SCRIPT_FILENAME")
+ jFast.properties.getProperty("PATH_INFO"));
}
path_translated = new URI("file", null, pt_path, null);
fs_root_uri = URI.create("file:/");
}
else {
path_translated = getPathTranslated(fs_root_uri, path, "/");
}
initRequest(request_method, request_uri, path_translated,
jFast.properties, fs_root_uri, false);
}
@Override public Integer handleResponse(Response response)
throws Exception {
try {
// Output HTTP headers
final PrintWriter out = new PrintWriter(IO.createWriter(jFast.out, "US-ASCII"));
out.println("Status: " + response.getStatus());
out.println("Content-Type: " + response.getContentType(true));
if (response.isBuffered()) {
out.println("Content-Length: " + response.getContentLength());
}
response.enumerateHeaders(new Response.HeaderEnumerator() {
public void header(String name, String value) {
out.println(name + ": " + value);
}
});
out.println();
out.flush();
response.writeResult(jFast.out);
getErrorWriter().flush();
jFast.out.flush();
return 0;
}
finally {
jFast.end();
}
}
public static void runServer(int fastcgi_port, final URI fs_root_uri)
throws IOException {
final ESXX esxx = ESXX.getInstance();
JFast jfast = new JFast(fastcgi_port);
esxx.getLogger().logp(Level.INFO, null, null,
"Listening for FastCGI requests on port " + fastcgi_port);
int timeout = (int) (Double.parseDouble(esxx.getSettings()
.getProperty("esxx.net.timeout", "60"))
* 1000);
while (true) {
try {
while (true) {
esxx.getLogger().log(Level.FINE, "Waiting for FastCGI connection");
final Socket accepted = jfast.accept();
accepted.setSoTimeout(timeout);
esxx.getLogger().log(Level.FINE, "Accepted FastCGI connection from " + accepted);
// Read JFast message in new thread with default timeout
esxx.addContextAction(null, new ContextAction() {
@Override public Object run(Context cx) {
Socket socket = accepted;
try {
esxx.getLogger().log(Level.FINE,
"Reading FastCGI request from " + accepted);
JFastRequest req = new JFastRequest(socket);
socket = null; // JFastRequest owns Socket
esxx.getLogger().log(Level.FINE,
"Initializing FastCGI request from " + accepted);
FCGIRequest fr = new FCGIRequest(req);
try {
// Fire and forget
fr.initRequest(fs_root_uri);
esxx.addRequest(fr, fr, 0);
req = null; // FCGIRequest owns JFastRequest
esxx.getLogger().log(Level.FINE,
"FastCGI request from " + accepted
+ " successfully initialized");
}
catch (Exception ex) {
fr.reportInternalError(500,
"ESXX Server Error", "FastCGI Error",
ex.getMessage(), ex);
req = null; // FCGIRequest owns JFastRequest
esxx.getLogger().log(Level.FINE,
"FastCGI request from " + accepted
+ " failed to initialize");
}
finally {
if (req != null) {
try { req.end(); } catch (Exception ignored) {}
}
}
}
catch (JFastException ex) {
// Invalid FCGI packet data etc
esxx.getLogger().log(Level.WARNING,
"Failed to process FastCGI request from " + accepted, ex);
}
catch (java.net.SocketTimeoutException ex) {
esxx.getLogger().logp(Level.WARNING, null, null,
"FastCGI socket timeout from " + accepted);
}
catch (IOException ex) {
// I/O error on Socket
esxx.getLogger().log(Level.SEVERE,
"I/O error when processing FastCGI request from "
+ accepted, ex);
}
finally {
if (socket != null) {
try { socket.close(); } catch (Exception ignored) {}
}
}
return null;
}
}, "FastCGI " + accepted, timeout);
}
}
catch (IOException ex) {
esxx.getLogger().log(Level.SEVERE,
"Failed to accept JFast request", ex);
// Re-bind
jfast.close();
jfast = new JFast(fastcgi_port);
}
}
}
private JFastRequest jFast;
}