blob: 10d605f916a0591989e9296e4aa93febbbc17f06 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2007 Boeing.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Boeing - initial API and implementation
*******************************************************************************/
package org.eclipse.osee.ote.classserver;
import java.io.BufferedInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import org.eclipse.osee.framework.logging.OseeLog;
public class ClassServer extends Thread {
private ServerSocket server;
private URL hostName;
private List<ResourceFinder> resourceFinders;
private ExecutorService socketThreads;
/**
* Construct a server
*
* @param port the port to use
* @throws IOException if the server socket cannot be created
*/
public ClassServer(int port, InetAddress address) throws IOException {
server = new ServerSocket(port, 50, address);
socketThreads = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable arg0) {
Thread th = new Thread(arg0, "ClassServer Task");
th.setDaemon(true);
return th;
}
});
if(address instanceof Inet6Address){
hostName = new URL("http://[" + address.getHostAddress() + "]:" + server.getLocalPort() + "/");
} else {
hostName = new URL("http://" + address.getHostAddress() + ":" + server.getLocalPort() + "/");
}
this.setName("OSEE ClassServer");
this.resourceFinders = new ArrayList<>();
}
public void addResourceFinder(ResourceFinder finder) {
this.resourceFinders.add(finder);
}
/**
* Spawn a thread for each connection requesting service
*/
@Override
public void run() {
String msg = "ClassServer started [";
msg += "port ";
msg += Integer.toString(getPort());
msg += "]";
OseeLog.log(ClassServer.class, Level.INFO, msg);
try {
while (true) {
Socket socket = server.accept();
socketThreads.submit(new Task(socket));
}
} catch (IOException e) {
synchronized (this) {
if (!server.isClosed()) {
OseeLog.log(ClassServer.class, Level.SEVERE, "accepting connection", e);
terminate();
OseeLog.log(ClassServer.class, Level.WARNING, "ClassServer Terminated");
}
}
}
}
/**
* Terminate Server - perform cleanup
*/
public synchronized void terminate() {
try {
server.close();
} catch (IOException e) {
// do nothing
}
final Iterator<ResourceFinder> iter = resourceFinders.iterator();
while (iter.hasNext()) {
final ResourceFinder resFinder = iter.next();
resFinder.dispose();
}
}
/** Returns the port on which this server is listening. */
public int getPort() {
return server.getLocalPort();
}
/** Read up to CRLF, return false if EOF */
private static boolean readLine(InputStream in, StringBuffer buf) throws IOException {
while (true) {
int c = in.read();
if (c < 0) {
return buf.length() > 0;
}
if (c == '\r') {
in.mark(1);
c = in.read();
if (c != '\n') {
in.reset();
}
return true;
}
if (c == '\n') {
return true;
}
buf.append((char) c);
}
}
/** Read the request/response and return the initial line. */
private static String getInput(Socket sock, boolean isRequest) throws IOException {
BufferedInputStream in = new BufferedInputStream(sock.getInputStream(), 256);
StringBuffer buf = new StringBuffer(80);
do {
if (!readLine(in, buf)) {
return null;
}
} while (isRequest && buf.length() == 0);
String initial = buf.toString();
do {
buf.setLength(0);
} while (readLine(in, buf) && buf.length() > 0);
return initial;
}
/**
* This method provides a way for subclasses to be notified when a file has been completely downloaded.
*
* @param fp The path to the file that was downloaded.
*/
protected void fileDownloaded(String fp, InetAddress addr) {
}
public URL getHostName() {
return hostName;
}
private class Task implements Runnable {// Thread {
private final Socket sock;
public Task(Socket sock) {
this.sock = sock;
// setDaemon(true);
}
/**
* Get bytes from path Returns the bytes of the requested file, or null if not found.
*/
private byte[] getBytes(String path) throws IOException {
byte[] bytes = null;
for (int i = 0; i < resourceFinders.size(); i++) {
ResourceFinder finder = resourceFinders.get(i);
bytes = finder.find(path);
if (bytes != null) {
return bytes;
}
}
OseeLog.log(ClassServer.class, Level.INFO, "requested file: '" + path + "' was not found.");
return null;
}
/** Parse % HEX HEX from s starting at i */
private char decode(String s, int i) {
return (char) Integer.parseInt(s.substring(i + 1, i + 3), 16);
}
private String getCanonicalizedPath(String path) {
try {
if (path.regionMatches(true, 0, "http://", 0, 7)) {
int i = path.indexOf('/', 7);
if (i < 0) {
path = "/";
} else {
path = path.substring(i);
}
}
for (int i = path.indexOf('%'); i >= 0; i = path.indexOf('%', i + 1)) {
char c = decode(path, i);
int n = 3;
if ((c & 0x80) != 0) {
switch (c >> 4) {
case 0xC:
case 0xD:
n = 6;
c = (char) ((c & 0x1F) << 6 | decode(path, i + 3) & 0x3F);
break;
case 0xE:
n = 9;
c = (char) ((c & 0x0f) << 12 | (decode(path, i + 3) & 0x3F) << 6 | decode(path, i + 6) & 0x3F);
break;
default:
return null;
}
}
path = path.substring(0, i) + c + path.substring(i + n);
}
} catch (Exception e) {
return null;
}
if (path.length() == 0 || path.charAt(0) != '/') {
return null;
}
return path.substring(1);
}
@Override
public void run() {
try {
DataOutputStream out = new DataOutputStream(sock.getOutputStream());
String req;
try {
req = getInput(sock, true);
} catch (Exception e) {
OseeLog.log(ClassServer.class, Level.INFO, "reading request", e);
return;
}
if (req == null) {
return;
}
if (req.startsWith("SHUTDOWN *")) {
out.writeBytes("HTTP/1.0 403 Forbidden\r\n\r\n");
out.flush();
return;
}
String[] args = null;
boolean get = req.startsWith("GET ");
if (!get && !req.startsWith("HEAD ")) {
OseeLog.log(ClassServer.class, Level.FINE, "bad request \"{0}\" from {1}:{2}");
out.writeBytes("HTTP/1.0 400 Bad Request\r\n\r\n");
out.flush();
return;
}
String path = req.substring(get ? 4 : 5);
int i = path.indexOf(' ');
if (i > 0) {
path = path.substring(0, i);
}
path = getCanonicalizedPath(path);
if (path == null) {
OseeLog.log(ClassServer.class, Level.FINE, "bad request \"{0}\" from {1}:{2}");
out.writeBytes("HTTP/1.0 400 Bad Request\r\n\r\n");
out.flush();
return;
}
if (args != null) {
args[0] = path;
}
OseeLog.log(ClassServer.class, Level.FINER,
get ? "{0} requested from {1}:{2}" : "{0} probed from {1}:{2}");
byte[] bytes;
try {
bytes = getBytes(path);
} catch (Exception e) {
OseeLog.log(ClassServer.class, Level.WARNING, "getting bytes", e);
out.writeBytes("HTTP/1.0 500 Internal Error\r\n\r\n");
out.flush();
return;
}
if (bytes == null) {
OseeLog.logf(ClassServer.class, Level.FINE, "%s not found", path);
out.writeBytes("HTTP/1.0 404 Not Found\r\n\r\n");
out.flush();
return;
}
out.writeBytes("HTTP/1.0 200 OK\r\n");
out.writeBytes("Content-Length: " + bytes.length + "\r\n");
out.writeBytes("Content-Type: application/java\r\n\r\n");
if (get) {
out.write(bytes);
}
out.flush();
if (get) {
fileDownloaded(path, sock.getInetAddress());
}
} catch (Exception e) {
OseeLog.log(ClassServer.class, Level.INFO, "writing response", e);
} finally {
try {
sock.close();
} catch (IOException e) {
}
}
}
}
}