/**
* The MIT License
* Copyright (c) 2010 Tad Glines
*
* 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
* AUTHORS OR COPYRIGHT HOLDERS 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.
*/
package com.glines.socketio.server.transport;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.util.IO;
import com.glines.socketio.server.SocketIOSession;
import com.glines.socketio.server.Transport;
public class FlashSocketTransport extends WebSocketTransport {
public static final String TRANSPORT_NAME = "flashsocket";
public static final String FLASHPOLICY_SERVER_HOST_KEY = "flashPolicyServerHost";
public static final String FLASHPOLICY_SERVER_PORT_KEY = "flashPolicyServerPort";
public static final String FLASHPOLICY_DOMAIN_KEY = "flashPolicyDomain";
public static final String FLASHPOLICY_PORTS_KEY = "flashPolicyPorts";
private static final String FLASHFILE_NAME = "WebSocketMain.swf";
private static final String FLASHFILE_PATH = TRANSPORT_NAME + "/" + FLASHFILE_NAME;
private ServerSocketChannel flashPolicyServer = null;
private ExecutorService executor = Executors.newCachedThreadPool();
private Future<?> policyAcceptorThread = null;
private String flashPolicyServerHost = null;
private short flashPolicyServerPort = 843;
private String flashPolicyDomain = null;
private String flashPolicyPorts = null;
public FlashSocketTransport(int bufferSize, int maxIdleTime) {
super(bufferSize, maxIdleTime);
}
@Override
public String getName() {
return TRANSPORT_NAME;
}
@Override
public void init(ServletConfig config) {
flashPolicyServerHost = config.getInitParameter(FLASHPOLICY_SERVER_HOST_KEY);
flashPolicyDomain = config.getInitParameter(FLASHPOLICY_DOMAIN_KEY);
flashPolicyPorts = config.getInitParameter(FLASHPOLICY_PORTS_KEY);
String port = config.getInitParameter(FLASHPOLICY_SERVER_PORT_KEY);
if (port != null) {
flashPolicyServerPort = Short.parseShort(port);
}
if (flashPolicyServerHost != null && flashPolicyDomain != null && flashPolicyPorts != null) {
try {
startFlashPolicyServer();
} catch (IOException e) {
// Ignore
}
}
}
@Override
public void destroy() {
stopFlashPolicyServer();
}
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
Transport.InboundFactory inboundFactory,
SocketIOSession.Factory sessionFactory)
throws IOException {
String path = request.getPathInfo();
if (path == null || path.length() == 0 || "/".equals(path)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid " + TRANSPORT_NAME + " transport request");
return;
}
if (path.startsWith("/")) path = path.substring(1);
String[] parts = path.split("/");
if ("GET".equals(request.getMethod()) && TRANSPORT_NAME.equals(parts[0])) {
if (!FLASHFILE_PATH.equals(path)) {
super.handle(request, response, inboundFactory, sessionFactory);
} else {
response.setContentType("application/x-shockwave-flash");
InputStream is = this.getClass().getClassLoader().getResourceAsStream("com/glines/socketio/" + FLASHFILE_NAME);
OutputStream os = response.getOutputStream();
try {
IO.copy(is, os);
} catch (IOException e) {
// TODO: Do we care?
}
}
} else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid " + TRANSPORT_NAME + " transport request");
}
}
/**
* Starts this server, binding to the previously passed SocketAddress.
*/
public void startFlashPolicyServer() throws IOException {
final String POLICY_FILE_REQUEST = "<policy-file-request/>";
flashPolicyServer = ServerSocketChannel.open();
flashPolicyServer.socket().setReuseAddress(true);
flashPolicyServer.socket().bind(new InetSocketAddress(flashPolicyServerHost, flashPolicyServerPort));
flashPolicyServer.configureBlocking(true);
// Spawn a new server acceptor thread, which must accept incoming
// connections indefinitely - until a ClosedChannelException is thrown.
policyAcceptorThread = executor.submit(new Runnable() {
@Override
public void run() {
try {
while (true) {
final SocketChannel serverSocket = flashPolicyServer.accept();
executor.submit(new Runnable() {
@Override
public void run() {
try {
serverSocket.configureBlocking(true);
Socket s = serverSocket.socket();
StringBuilder request = new StringBuilder();
InputStreamReader in = new InputStreamReader(s.getInputStream());
int c;
while ((c = in.read()) != 0 && request.length() <= POLICY_FILE_REQUEST.length()) {
request.append((char)c);
}
if (request.toString().equalsIgnoreCase(POLICY_FILE_REQUEST) ||
flashPolicyDomain != null && flashPolicyPorts != null) {
PrintWriter out = new PrintWriter(s.getOutputStream());
out.println("<cross-domain-policy><allow-access-from domain=\""+flashPolicyDomain+"\" to-ports=\""+flashPolicyPorts+"\" /></cross-domain-policy>");
out.write(0);
out.flush();
}
serverSocket.close();
} catch (IOException e) {
// TODO: Add loging
} finally {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore error on close.
}
}
}
});
}
} catch (ClosedChannelException e) {
return;
} catch (IOException e) {
throw new IllegalStateException("Server should not throw a misunderstood IOException", e);
}
}
});
}
private void stopFlashPolicyServer() {
if (flashPolicyServer != null) {
try {
flashPolicyServer.close();
} catch (IOException e) {
// Ignore
}
}
if (policyAcceptorThread != null) {
try {
policyAcceptorThread.get();
} catch (InterruptedException e) {
throw new IllegalStateException();
} catch (ExecutionException e) {
throw new IllegalStateException("Server thread threw an exception", e.getCause());
}
if (!policyAcceptorThread.isDone()) {
throw new IllegalStateException("Server acceptor thread has not stopped.");
}
}
}
}