All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.globus.io.gass.server.GassServer Maven / Gradle / Ivy

/*
 * Copyright 1999-2006 University of Chicago
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.globus.io.gass.server;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.Hashtable;

import org.globus.util.GlobusURL;
import org.globus.util.http.HttpResponse;
import org.globus.io.gass.client.internal.GASSProtocol;
import org.globus.net.BaseServer;
import org.globus.net.SocketFactory;
import org.globus.gsi.GSIConstants;
import org.globus.gsi.gssapi.auth.SelfAuthorization;
import org.globus.gsi.gssapi.auth.AuthorizationException;
import org.globus.gsi.gssapi.GSSConstants;
import org.globus.gsi.gssapi.net.GssSocket;
import org.globus.gsi.gssapi.net.GssSocketFactory;

import org.gridforum.jgss.ExtendedGSSManager;
import org.gridforum.jgss.ExtendedGSSContext;

import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * The GassServer class acts as a basic multi-threaded HTTPS
 * server that handles GASS requests.
 *
 * @version $Revision 1.21 $
 */
public class GassServer extends BaseServer {

    private static Log logger =
        LogFactory.getLog(GassServer.class.getName());
    
    public static final int READ_ENABLE            = 8;
    public static final int WRITE_ENABLE           = 16;
    public static final int STDOUT_ENABLE          = 32;
    public static final int STDERR_ENABLE          = 64;
    public static final int CLIENT_SHUTDOWN_ENABLE = 128;
  
    public static final String SHUTDOWN_STR = "/dev/globus_gass_client_shutdown";

    private Hashtable jobOutputs = null;
    private int options          = 0;
    
    /**
     * Starts Gass Server with default user credentials.
     * Port of the server will be dynamically assigned
     */
    public GassServer() 
	throws IOException {
	this(null, 0);
    }
  
    /**
     * Starts Gass Server on given port with default user credentials.
     *
     * @param port
     *         port of the server, if 0 it will be dynamically assigned
     */
    public GassServer(int port) 
	throws IOException {
	this(null, port);
    }

    /**
     * Starts Gass Server on given port and given credentials.
     *
     * @param cred
     *         credentials to use. if null default user credentials
     *         will be used
     * @param port
     *         port of the server, if 0 it will be dynamically assigned
     */
    public GassServer(GSSCredential cred, int port)
	throws IOException {
	super(cred, port);
	init();
    }

    /**
     * Starts Gass Server on given port and mode.
     * If secure mode, it will use default user credentials
     *
     * @param secure 
     *         if true starts server in secure mode, otherwise unsecure
     * @param port
     *         port of the server, if 0 it will be dynamically assigned
     */
    public GassServer(boolean secure, int port) 
	throws IOException {
	super(secure, port);
	init();
    }
  
    private void init() {
	jobOutputs = new Hashtable();
	options = READ_ENABLE | WRITE_ENABLE | STDOUT_ENABLE | STDERR_ENABLE;
	super.initialize();
	setAuthorization(SelfAuthorization.getInstance());
    }

    /**
     * Sets the options of the gass server such 
     * as enabling client shutdown, etc.
     *
     * @param options server options 
     */
    public void setOptions(int options) {
	this.options = options;
    }
  
    /**
     * Returns current options of the server.
     *
     * @return options of the server. O if not
     *         none set.
     */
    public int getOptions() {
	return options;
    }
    
    /**
     * Registers a output stream with a job. This is
     * used for job stdout/err redirection.
     * The label of the job should be the ending of the
     * job redirected url. For example, given following RSL
     * (stdout=$(GASS_URL)/dev/stdout-5) the label to register
     * the output stream with should be 'out-5'. 
     *
     * @param lb job label as described above.
     * @param out the output stream to redirect output to.
     */
    public void registerJobOutputStream(String lb, OutputStream out) {
	jobOutputs.put(lb, out);
    }

    /**
     * Unregisters a job output stream for specified output label. See
     * registerJobOutputStream() for more details.
     *
     * @param lb job output label.
     */
    public void unregisterJobOutputStream(String lb) {
	jobOutputs.remove(lb);
    }
    
    /**
     * Unregisters a job output stream. This method is deprecated.
     */
    public void unregisterJobOutputStream(String lb, OutputStream out) {
	unregisterJobOutputStream(lb);
    }
    
    protected OutputStream getJobOutputStream(String id) {
	return (OutputStream)jobOutputs.get(id);
    }
    
    protected void handleConnection(Socket socket) {
	GassClientHandler gcb = new GassClientHandler(this, 
						      socket);
	(new Thread(gcb)).start();
    }
  
    public String toString() {
	StringBuffer buf = new StringBuffer("GassServer: ");
	try {
	    buf.append(getURL());
	} catch(Exception e) {}
	buf.append(" options (");
	boolean op = ((options & READ_ENABLE) != 0);
	buf.append("r:" + ( (op) ? "+" : "-" ));
	op = ((options & WRITE_ENABLE) != 0);
	buf.append(" w:" + ( (op) ? "+" : "-" ));
	op = ((options & STDOUT_ENABLE) != 0);
	buf.append(" so:" + ( (op) ? "+" : "-"));
	op = ((options & STDERR_ENABLE) != 0);
	buf.append(" se:" + ( (op) ? "+" : "-"));
	op = ((options & CLIENT_SHUTDOWN_ENABLE) != 0);
	buf.append(" rc:" + ( (op) ? "+" : "-"));
	buf.append(")");
	return buf.toString();
    }
  
    /**
     * Shutdowns a remote gass server. The server must have the
     * CLIENT_SHUTDOWN option enabled for this to work.
     *
     * @param  cred    credentials to use.
     * @param  gassURL the url of the remote gass server.
     */
    public static void shutdown(GSSCredential cred, GlobusURL gassURL) 
	throws IOException, GSSException {
	
	OutputStream output     = null;
	InputStream input       = null;
	Socket socket           = null;
	
	try {

	    if (gassURL.getProtocol().equalsIgnoreCase("https")) {

		GSSManager manager = ExtendedGSSManager.getInstance();
		
		ExtendedGSSContext context = 
		    (ExtendedGSSContext)manager.createContext(null,
							      GSSConstants.MECH_OID,
							      cred,
							      GSSContext.DEFAULT_LIFETIME);

		context.setOption(GSSConstants.GSS_MODE,
				  GSIConstants.MODE_SSL);

		GssSocketFactory factory = GssSocketFactory.getDefault();

		socket = factory.createSocket(gassURL.getHost(), 
					      gassURL.getPort(),
					      context);

		((GssSocket)socket).setAuthorization(SelfAuthorization.getInstance());
	    } else {
                SocketFactory factory = SocketFactory.getDefault();
		socket = factory.createSocket(gassURL.getHost(), 
                                              gassURL.getPort());
	    }
	
	    output = socket.getOutputStream();
	    input  = socket.getInputStream();
	
	    String msg =  GASSProtocol.SHUTDOWN(SHUTDOWN_STR,
						gassURL.getHost());
	
	    
            if (logger.isTraceEnabled()) {
                logger.trace("Shutdown msg: " + msg);
            }
	
	    output.write( msg.getBytes() );
	    output.flush();
	
	    HttpResponse rp = new HttpResponse(input);
	    if (rp.httpCode == -1 && rp.httpMsg == null) {
		/* this is a workaround for C gass-server.
		 * The server just shuts down - it does
		 * not send the reply */
	    } else if (rp.httpCode != 200) {
		throw new IOException("Remote shutdown failed (" + rp.httpCode + " " + rp.httpMsg + ")");
	    }
	    
	} finally {
	    try {
		if (output != null) output.close();
		if (input != null) input.close();
		if (socket != null) socket.close();
	    } catch(Exception e) {}
	}
    }
	
}

class GassClientHandler implements Runnable {

    private static Log logger =
        LogFactory.getLog(GassClientHandler.class.getName());

    private static final boolean DEBUG_ON = false;

    private static final String CRLF = "\r\n";
    private static final String OKHEADER = "HTTP/1.1 200 OK\r\n";
    private static final String SERVER = "Server: Globus-GASS-HTTP/1.1.0\r\n";
    private static final String CONTENT_LENGTH = "Content-Length:";
    private static final String TRANSFER_ENCODING = "Transfer-Encoding: chunked";
    private static final String JAVA_CLIENT = "User-Agent: Java-Globus-GASS-HTTP/1.1.0";
    private static final String HTTP_CONTINUE = "HTTP/1.1 100 Continue\r\n";
    
    private static final String CONTENT_BINARY = "Content-Type: application/octet-stream" + CRLF;
    private static final String CONTENT_HTML   = "Content-Type: text/html" + CRLF;
    private static final String CONTENT_TEXT   = "Content-Type: text/plain" + CRLF;
    
    private static final String CONNECTION_CLOSE = "Connection: close\r\n";
    
    private static final String HEADER404 = "HTTP/1.1 404 File Not Found\r\n";

    private static final String MSG404 =
        "404 File Not Found\r\n" +
        "

404 File Not Found

\r\n"; private int BUFFER_SIZE = 4096; private GassServer server; private Socket socket; private int options; public GassClientHandler(GassServer server, Socket socket) { this.server = server; this.socket = socket; this.options = server.getOptions(); } private void write(OutputStream out, String msg) throws IOException { out.write(msg.getBytes()); out.flush(); } private void writeln(OutputStream out, String msg) throws IOException { out.write(msg.getBytes()); out.write(SERVER.getBytes()); out.write(CRLF.getBytes()); out.flush(); } /** * Listen on the server socket for a client, start another thread to * keep listening on the server socket, then deal with the client. */ public void run() { InputStream in = null; OutputStream out = null; try { in = socket.getInputStream(); out = socket.getOutputStream(); try { String line; line = readLine(in); if (DEBUG_ON) debug("header: " + line); if (line.startsWith("GET") && (options & GassServer.READ_ENABLE) != 0) { // copy to client String path = line.substring(4, line.indexOf(' ', 4) ); do { line = readLine(in); if (DEBUG_ON) debug("header (get): " + line); } while ( (line.length() != 0) && (line.charAt(0) != '\r') && (line.charAt(0) != '\n') ); transfer(out, path); } else if (line.startsWith("PUT") && ( ((options & GassServer.WRITE_ENABLE) != 0) || ((options & GassServer.CLIENT_SHUTDOWN_ENABLE) != 0) )) { // copy from client String path = line.substring(4, line.indexOf(' ', 4) ); transfer(in, path, false, out); writeln(out, OKHEADER); } else if (line.startsWith("POST") && ( ((options & GassServer.WRITE_ENABLE) != 0) || ((options & GassServer.STDOUT_ENABLE) != 0) || ((options & GassServer.STDERR_ENABLE) != 0) || ((options & GassServer.CLIENT_SHUTDOWN_ENABLE) != 0) )) { // append from client int index = line.indexOf('?') + 1; String path = line.substring(index, line.indexOf(' ', index) ); transfer(in, path, true, out); writeln(out, OKHEADER); } else { writeln(out, "HTTP/1.1 400 Bad Request" + CRLF); } } catch (FileNotFoundException ex) { logger.debug("FileNotFoundException occured: " + ex.getMessage(), ex); StringBuffer buf = new StringBuffer(HEADER404) .append(CONNECTION_CLOSE) .append(SERVER) .append(CONTENT_HTML) .append(CONTENT_LENGTH).append(" ").append(MSG404.length()) .append(CRLF).append(CRLF) .append(MSG404); out.write(buf.toString().getBytes()); out.flush(); } catch (AuthorizationException ex) { logger.debug("Exception occured: Authorization failed"); writeln(out, "HTTP/1.1 401 Authorization Failed" + CRLF); } catch (Exception ex) { logger.debug("Exception occured: " + ex.getMessage(), ex); writeln(out, "HTTP/1.1 400 " + ex.getMessage() + CRLF); } } catch (IOException e) { logger.error("Error writing response: " + e.getMessage(), e); } finally { try { socket.close(); } catch (IOException e) {} } } private String decodeUrlPath(String path) { if (path.length() == 0) return path; if (path.charAt(0) == '/') path = path.substring(1); try { return URLDecoder.decode(path); } catch(Exception e) { return path; } } /** * Transfer from a file, given its path, to the given OutputStream. * The BufferedWriter points to the same stream but is used to write * HTTP header information. */ private void transfer(OutputStream os, String path) throws IOException { path = decodeUrlPath(path); File f = new File(path); FileInputStream file = new FileInputStream(f); long length = f.length(); StringBuffer buf = new StringBuffer(OKHEADER) .append(CONNECTION_CLOSE) .append(SERVER) .append(CONTENT_BINARY) .append(CONTENT_LENGTH).append(" ").append(length) .append(CRLF).append(CRLF); os.write(buf.toString().getBytes()); os.flush(); byte [] buffer = new byte[BUFFER_SIZE]; int read; while (length != 0) { read = file.read(buffer); if (read == -1) break; os.write(buffer, 0, read); length -= read; } file.close(); os.flush(); os.close(); } private OutputStream pickOutputStream(String path, String str, OutputStream def) { int strl = str.length(); int pos = path.indexOf(str); if (pos != -1) { OutputStream out = server.getJobOutputStream(path.substring(pos + strl - 3)); if (out == null) { return def; } else { return out; } } return null; } /** * Transfer from the given InputStream to a file, given its path. * The Reader points to the same stream but is used to read * the HTTP header information. */ private void transfer(InputStream is, String path, boolean append, OutputStream outs) throws IOException { if (((options & GassServer.CLIENT_SHUTDOWN_ENABLE) != 0) && path.indexOf(GassServer.SHUTDOWN_STR) != -1) { server.shutdown(); return; } OutputStream out = null; String line; long length = 0; boolean chunked = false; boolean javaclient = false; do { line = readLine(is); if (DEBUG_ON) debug("header (put/post): " + line); if (line.startsWith(CONTENT_LENGTH)) { length = Long.parseLong( line.substring( line.indexOf(':') + 1 ).trim() ); } else if (line.startsWith(TRANSFER_ENCODING)) { chunked = true; } else if (line.startsWith(JAVA_CLIENT)) { javaclient = true; } } while ( (line.length() != 0) && (line.charAt(0) != '\r') && (line.charAt(0) != '\n') ); out = pickOutputStream(path, "/dev/stdout", System.out); if (out != null) { // this is stdout if ( (options & GassServer.STDOUT_ENABLE) == 0 ) { throw new IOException("Bad Request"); } } else { out = pickOutputStream(path, "/dev/stderr", System.err); if (out != null) { // this is stderr if ( (options & GassServer.STDERR_ENABLE) == 0 ) { throw new IOException("Bad Request"); } } else { // this is a file if ( (options & GassServer.WRITE_ENABLE) == 0 ) { throw new IOException("Bad Request"); } path = decodeUrlPath(path); out = new FileOutputStream(path, append); } } if (javaclient) { writeln(outs, HTTP_CONTINUE); } byte [] buffer = new byte[BUFFER_SIZE]; int read; if (!chunked) { while (length != 0) { read = is.read(buffer); if (read == -1) break; out.write(buffer, 0, read); length -= read; } } else { /* * Chunks are of the form * * lengthCRLF * dataCRLF * * which can be repeated ad infinitum until we meet * * 0CRLF * CRLF * * NOTE: length is represented in hex! * */ long chunkLength; int bytes; do { line = readLine(is); length = fromHex(line); if (DEBUG_ON) debug("chunk: '" + line + "' size:" + length); chunkLength = length; while (chunkLength != 0) { if (chunkLength > buffer.length) { bytes = buffer.length; } else { bytes = (int)chunkLength; } read = is.read(buffer, 0, bytes); if (read == -1) break; out.write(buffer, 0, read); chunkLength -= read; } is.read(); // skip CR is.read(); // skip LF } while (length > 0); if (DEBUG_ON) debug("finished chunking"); } out.flush(); // do not close System.out or System.err! if (out == System.out || out == System.err) return; out.close(); } /** * Read a line of text from the given Stream and return it * as a String. Assumes lines end in CRLF. */ private String readLine(InputStream in) throws IOException { StringBuffer buf = new StringBuffer(); int c, length = 0; while(true) { c = in.read(); if (c == -1 || c == '\n' || length > 512) { break; } else if (c == '\r') { in.read(); return buf.toString(); } else { buf.append((char)c); length++; } } return buf.toString(); } /** * Convert a String representing a hex number to a long. */ private long fromHex(String s) { long result = 0; int size = s.length(); for (int i = 0; i < size; i++) { char c = s.charAt(i); result *= 16; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': result += (c - '0'); break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': result += (c - 'a' + 10); break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': result += (c - 'A' + 10); break; default : // TODO: throw a ParseException } } return result; } private void debug(String msg) { System.err.println(msg); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy