From 386e8907a7ed52cc0c98f33d121f5e7b971f33a3 Mon Sep 17 00:00:00 2001 From: "marcins78@gmail.com" Date: Wed, 9 Jan 2013 03:56:07 +0000 Subject: [PATCH] Working on the async lotro server. --- .../lotro/async/HallRequestHandler.java | 262 +++++++++++++++++ .../lotro/async/HttpProcessingException.java | 13 + .../lotro/async/HttpRequestHandler.java | 104 ------- .../lotro/async/LoggedUserHolder.java | 49 ++++ .../lotro/async/LoginRequestHandler.java | 49 ++++ .../lotro/async/LotroAsyncServer.java | 4 +- .../lotro/async/LotroHttpRequestHandler.java | 269 ++++++++++++++++++ ...y.java => LotroServerPipelineFactory.java} | 26 +- .../async/LotroServerRequestHandler.java | 74 +++++ .../gempukku/lotro/async/ResponseWriter.java | 18 ++ .../lotro/async/RootUriRequestHandler.java | 37 +++ .../lotro/async/StatusRequestHandler.java | 41 +++ .../lotro/async/UriRequestHandler.java | 11 + .../lotro/async/WebRequestHandler.java | 29 ++ .../src/main/resources/test-log4j.xml | 43 +++ .../lotro/collection/CollectionsManager.java | 4 +- 16 files changed, 918 insertions(+), 115 deletions(-) create mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HallRequestHandler.java create mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HttpProcessingException.java delete mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HttpRequestHandler.java create mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LoggedUserHolder.java create mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LoginRequestHandler.java create mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroHttpRequestHandler.java rename gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/{HttpServerPipelineFactory.java => LotroServerPipelineFactory.java} (77%) create mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroServerRequestHandler.java create mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/ResponseWriter.java create mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/RootUriRequestHandler.java create mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/StatusRequestHandler.java create mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/UriRequestHandler.java create mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/WebRequestHandler.java create mode 100644 gemp-lotr/gemp-lotr-async/src/main/resources/test-log4j.xml diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HallRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HallRequestHandler.java new file mode 100644 index 000000000..b19da8282 --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HallRequestHandler.java @@ -0,0 +1,262 @@ +package com.gempukku.lotro.async; + +import com.gempukku.lotro.SubscriptionConflictException; +import com.gempukku.lotro.SubscriptionExpiredException; +import com.gempukku.lotro.collection.CollectionsManager; +import com.gempukku.lotro.db.vo.League; +import com.gempukku.lotro.game.LotroFormat; +import com.gempukku.lotro.game.Player; +import com.gempukku.lotro.game.formats.LotroFormatLibrary; +import com.gempukku.lotro.hall.HallChannelVisitor; +import com.gempukku.lotro.hall.HallServer; +import com.gempukku.lotro.league.LeagueSerieData; +import com.gempukku.lotro.league.LeagueService; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.handler.codec.http.HttpMethod; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.QueryStringDecoder; +import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.lang.reflect.Type; +import java.util.Map; + +public class HallRequestHandler extends LotroServerRequestHandler implements UriRequestHandler { + private CollectionsManager _collectionManager; + private LotroFormatLibrary _formatLibrary; + private HallServer _hallServer; + private LeagueService _leagueService; + private long _longPollingLength = 5000; + private long _longPollingInterval = 200; + + public HallRequestHandler(Map context) { + super(context); + _collectionManager = extractObject(context, CollectionsManager.class); + _formatLibrary = extractObject(context, LotroFormatLibrary.class); + _hallServer = extractObject(context, HallServer.class); + _leagueService = extractObject(context, LeagueService.class); + } + + @Override + public void handleRequest(String uri, HttpRequest request, Map context, ResponseWriter responseWriter, MessageEvent e) { + if (uri.equals("") && request.getMethod() == HttpMethod.GET) { + getHall(request, responseWriter); + } else if (uri.equals("/update") && request.getMethod() == HttpMethod.POST) { + updateHall(request, responseWriter); + } else { + responseWriter.writeError(404); + } + } + + private void getHall(HttpRequest request, ResponseWriter responseWriter) { + QueryStringDecoder queryDecoder = new QueryStringDecoder(request.getUri()); + + String participantId = getQueryParameterSafely(queryDecoder, "participantId"); + + try { + Player resourceOwner = getResourceOwnerSafely(request, participantId); + + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + + Document doc = documentBuilder.newDocument(); + + Element hall = doc.createElement("hall"); + hall.setAttribute("currency", String.valueOf(_collectionManager.getPlayerCollection(resourceOwner, "permanent").getCurrency())); + + _hallServer.signupUserForHall(resourceOwner, new SerializeHallInfoVisitor(doc, hall)); + for (Map.Entry format : _formatLibrary.getHallFormats().entrySet()) { + Element formatElem = doc.createElement("format"); + formatElem.setAttribute("type", format.getKey()); + formatElem.appendChild(doc.createTextNode(format.getValue().getName())); + hall.appendChild(formatElem); + } + for (League league : _leagueService.getActiveLeagues()) { + final LeagueSerieData currentLeagueSerie = _leagueService.getCurrentLeagueSerie(league); + if (currentLeagueSerie != null && _leagueService.isPlayerInLeague(league, resourceOwner)) { + Element formatElem = doc.createElement("format"); + formatElem.setAttribute("type", league.getType()); + formatElem.appendChild(doc.createTextNode(league.getName())); + hall.appendChild(formatElem); + } + } + + doc.appendChild(hall); + + responseWriter.writeResponse(doc); + } catch (HttpProcessingException exp) { + responseWriter.writeError(exp.getStatus()); + } catch (Exception exp) { + responseWriter.writeError(500); + } + } + + private void updateHall(HttpRequest request, ResponseWriter responseWriter) { + try { + HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder(request); + String participantId = getFormParameterSafely(postDecoder, "participantId"); + int channelNumber = Integer.parseInt(getFormParameterSafely(postDecoder, "channelNumber")); + + Player resourceOwner = getResourceOwnerSafely(request, participantId); + + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + + Document doc = documentBuilder.newDocument(); + + Element hall = doc.createElement("hall"); + + try { + // Use long polling + long start = System.currentTimeMillis(); + while (System.currentTimeMillis() < start + _longPollingLength && !_hallServer.hasChanges(resourceOwner, channelNumber)) { + try { + Thread.sleep(_longPollingInterval); + } catch (InterruptedException exp) { + + } + } + _hallServer.processHall(resourceOwner, channelNumber, new SerializeHallInfoVisitor(doc, hall)); + } catch (SubscriptionExpiredException exp) { + throw new WebApplicationException(Response.Status.GONE); + } catch (SubscriptionConflictException exp) { + throw new WebApplicationException(Response.Status.CONFLICT); + } + hall.setAttribute("currency", String.valueOf(_collectionManager.getPlayerCollection(resourceOwner, "permanent").getCurrency())); + + doc.appendChild(hall); + responseWriter.writeResponse(doc); + } catch (HttpProcessingException exp) { + responseWriter.writeError(exp.getStatus()); + } catch (Exception exp) { + responseWriter.writeError(500); + } + } + + + private class SerializeHallInfoVisitor implements HallChannelVisitor { + private Document _doc; + private Element _hall; + + public SerializeHallInfoVisitor(Document doc, Element hall) { + _doc = doc; + _hall = hall; + } + + @Override + public void channelNumber(int channelNumber) { + _hall.setAttribute("channelNumber", String.valueOf(channelNumber)); + } + + @Override + public void serverTime(String serverTime) { + _hall.setAttribute("serverTime", serverTime); + } + + @Override + public void motdChanged(String motd) { + _hall.setAttribute("motd", motd); + } + + @Override + public void addTournamentQueue(String queueId, Map props) { + Element queue = _doc.createElement("queue"); + queue.setAttribute("action", "add"); + queue.setAttribute("id", queueId); + for (Map.Entry attribute : props.entrySet()) + queue.setAttribute(attribute.getKey(), attribute.getValue()); + _hall.appendChild(queue); + } + + @Override + public void updateTournamentQueue(String queueId, Map props) { + Element queue = _doc.createElement("queue"); + queue.setAttribute("action", "update"); + queue.setAttribute("id", queueId); + for (Map.Entry attribute : props.entrySet()) + queue.setAttribute(attribute.getKey(), attribute.getValue()); + _hall.appendChild(queue); + } + + @Override + public void removeTournamentQueue(String queueId) { + Element queue = _doc.createElement("queue"); + queue.setAttribute("action", "remove"); + queue.setAttribute("id", queueId); + _hall.appendChild(queue); + } + + @Override + public void addTournament(String tournamentId, Map props) { + Element tournament = _doc.createElement("tournament"); + tournament.setAttribute("action", "add"); + tournament.setAttribute("id", tournamentId); + for (Map.Entry attribute : props.entrySet()) + tournament.setAttribute(attribute.getKey(), attribute.getValue()); + _hall.appendChild(tournament); + } + + @Override + public void updateTournament(String tournamentId, Map props) { + Element tournament = _doc.createElement("tournament"); + tournament.setAttribute("action", "update"); + tournament.setAttribute("id", tournamentId); + for (Map.Entry attribute : props.entrySet()) + tournament.setAttribute(attribute.getKey(), attribute.getValue()); + _hall.appendChild(tournament); + } + + @Override + public void removeTournament(String tournamentId) { + Element tournament = _doc.createElement("tournament"); + tournament.setAttribute("action", "remove"); + tournament.setAttribute("id", tournamentId); + _hall.appendChild(tournament); + } + + @Override + public void addTable(String tableId, Map props) { + Element table = _doc.createElement("table"); + table.setAttribute("action", "add"); + table.setAttribute("id", tableId); + for (Map.Entry attribute : props.entrySet()) + table.setAttribute(attribute.getKey(), attribute.getValue()); + _hall.appendChild(table); + } + + @Override + public void updateTable(String tableId, Map props) { + Element table = _doc.createElement("table"); + table.setAttribute("action", "update"); + table.setAttribute("id", tableId); + for (Map.Entry attribute : props.entrySet()) + table.setAttribute(attribute.getKey(), attribute.getValue()); + _hall.appendChild(table); + } + + @Override + public void removeTable(String tableId) { + Element table = _doc.createElement("table"); + table.setAttribute("action", "remove"); + table.setAttribute("id", tableId); + _hall.appendChild(table); + } + + @Override + public void runningPlayerGame(String gameId) { + Element runningGame = _doc.createElement("game"); + runningGame.setAttribute("id", gameId); + _hall.appendChild(runningGame); + } + + @Override + public void playerBusy(boolean busy) { + _hall.setAttribute("busy", String.valueOf(busy)); + } + } +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HttpProcessingException.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HttpProcessingException.java new file mode 100644 index 000000000..7c42dff6b --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HttpProcessingException.java @@ -0,0 +1,13 @@ +package com.gempukku.lotro.async; + +public class HttpProcessingException extends Exception { + private int _status; + + public HttpProcessingException(int status) { + _status = status; + } + + public int getStatus() { + return _status; + } +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HttpRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HttpRequestHandler.java deleted file mode 100644 index 6867f0eba..000000000 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HttpRequestHandler.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.gempukku.lotro.async; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.channel.*; -import org.jboss.netty.handler.codec.http.*; -import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH; -import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; -import static org.jboss.netty.handler.codec.http.HttpHeaders.is100ContinueExpected; -import static org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive; -import static org.jboss.netty.handler.codec.http.HttpResponseStatus.*; -import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1; -import org.jboss.netty.util.CharsetUtil; - -import java.lang.reflect.Type; -import java.util.List; -import java.util.Map; - -public class HttpRequestHandler extends SimpleChannelUpstreamHandler { - - private HttpRequest _request; - private boolean _readingChunks; - private String _uri; - private Map _objects; - - public HttpRequestHandler(Map objects) { - _objects = objects; - } - - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { - _request = (HttpRequest) e.getMessage(); - - if (is100ContinueExpected(_request)) { - send100Continue(e); - } - - String uri = _request.getUri(); - - if (uri.indexOf("?")>-1) - _uri = uri.substring(0, uri.indexOf("?")); - else - _uri = uri; - - QueryStringDecoder queryStringDecoder = new QueryStringDecoder(_request.getUri()); - Map> queryStringParams = queryStringDecoder.getParameters(); - - HttpMethod httpMethod = _request.getMethod(); - - if (_request.isChunked()) { - send400Error(e); - } else { - ChannelBuffer content = _request.getContent(); - if (content.readable()) { - } - writeResponse(e); - } - } - - private void writeResponse(MessageEvent e) { - // Decide whether to close the connection or not. - boolean keepAlive = isKeepAlive(_request); - - // Build the response object. - HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); - response.setContent(ChannelBuffers.copiedBuffer("Response: " + _uri, CharsetUtil.UTF_8)); - response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8"); - - if (keepAlive) { - // Add 'Content-Length' header only for a keep-alive connection. - response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes()); - } - - // Write the response. - ChannelFuture future = e.getChannel().write(response); - - // Close the non-keep-alive connection after the write operation is done. - if (!keepAlive) { - future.addListener(ChannelFutureListener.CLOSE); - } - } - - private void send400Error(MessageEvent e) { - boolean keepAlive = isKeepAlive(_request); - - HttpResponse response = new DefaultHttpResponse(HTTP_1_1, BAD_REQUEST); - ChannelFuture future = e.getChannel().write(response); - if (!keepAlive) { - future.addListener(ChannelFutureListener.CLOSE); - } - } - - private void send100Continue(MessageEvent e) { - HttpResponse response = new DefaultHttpResponse(HTTP_1_1, CONTINUE); - e.getChannel().write(response); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) - throws Exception { - e.getCause().printStackTrace(); - e.getChannel().close(); - } -} diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LoggedUserHolder.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LoggedUserHolder.java new file mode 100644 index 000000000..1f6dfcc94 --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LoggedUserHolder.java @@ -0,0 +1,49 @@ +package com.gempukku.lotro.async; + +import org.jboss.netty.handler.codec.http.Cookie; +import org.jboss.netty.handler.codec.http.CookieDecoder; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.COOKIE; +import org.jboss.netty.handler.codec.http.HttpRequest; + +import java.util.*; + +public class LoggedUserHolder { + private Map _users = Collections.synchronizedMap(new HashMap()); + + public String getLoggedUser(HttpRequest request) { + CookieDecoder cookieDecoder = new CookieDecoder(); + Set cookies = cookieDecoder.decode(request.getHeader(COOKIE)); + for (Cookie cookie : cookies) { + if (cookie.getName().equals("loggedUser")) { + String value = cookie.getValue(); + if (value != null) { + String loggedUser = _users.get(value); + if (loggedUser != null) + return loggedUser; + } + } + } + return null; + } + + public Map logUser(String userName) { + Map cookies = new HashMap(); + cookies.put("loggedUser", insertValueForUser(userName)); + return cookies; + } + + private char[] _chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray(); + + private synchronized String insertValueForUser(String userName) { + Random rnd = new Random(); + String randomStr; + do { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < 20; i++) + result.append(_chars[rnd.nextInt(_chars.length)]); + randomStr = result.toString(); + } while (_users.containsKey(randomStr)); + _users.put(randomStr, userName); + return randomStr; + } +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LoginRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LoginRequestHandler.java new file mode 100644 index 000000000..d3f63473a --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LoginRequestHandler.java @@ -0,0 +1,49 @@ +package com.gempukku.lotro.async; + +import com.gempukku.lotro.game.Player; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.handler.codec.http.HttpMethod; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder; + +import java.lang.reflect.Type; +import java.sql.SQLException; +import java.util.Map; + +public class LoginRequestHandler extends LotroServerRequestHandler implements UriRequestHandler { + public LoginRequestHandler(Map context) { + super(context); + } + + @Override + public void handleRequest(String uri, HttpRequest request, Map context, ResponseWriter responseWriter, MessageEvent e) { + try { + if (uri.equals("") && request.getMethod() == HttpMethod.POST) { + HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder(request); + String login = getFormParameterSafely(postDecoder, "login"); + String password = getFormParameterSafely(postDecoder, "password"); + + Player player = _playerDao.loginUser(login, password); + if (player != null) { + if (player.getType().contains("u")) { + responseWriter.writeResponse(null, logUserReturningCookies(e, login)); + } else { + responseWriter.writeError(403); + } + } else { + responseWriter.writeError(401); + } + + } else { + responseWriter.writeError(404); + } + } catch (Exception exp) { + responseWriter.writeError(500); + } + } + + private Map logUserReturningCookies(MessageEvent e, String login) throws SQLException { + _playerDao.updateLastLoginIp(login, e.getChannel().getRemoteAddress().toString()); + return _loggedUserHolder.logUser(login); + } +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroAsyncServer.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroAsyncServer.java index 091832203..36cbb27e3 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroAsyncServer.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroAsyncServer.java @@ -17,10 +17,10 @@ public class LotroAsyncServer { ServerBootstrap bootstrap = new ServerBootstrap(factory); - bootstrap.setPipelineFactory(new HttpServerPipelineFactory()); + bootstrap.setPipelineFactory(new LotroServerPipelineFactory()); bootstrap.setOption("child.tcpNoDelay", true); bootstrap.setOption("child.keepAlive", true); - bootstrap.bind(new InetSocketAddress(8080)); + bootstrap.bind(new InetSocketAddress(80)); } } diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroHttpRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroHttpRequestHandler.java new file mode 100644 index 000000000..f7057fb48 --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroHttpRequestHandler.java @@ -0,0 +1,269 @@ +package com.gempukku.lotro.async; + +import org.apache.log4j.Logger; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.*; +import org.jboss.netty.handler.codec.http.*; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*; +import static org.jboss.netty.handler.codec.http.HttpHeaders.is100ContinueExpected; +import static org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive; +import static org.jboss.netty.handler.codec.http.HttpResponseStatus.*; +import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import org.jboss.netty.util.CharsetUtil; +import org.w3c.dom.Document; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.*; +import java.lang.reflect.Type; +import java.util.Map; + +public class LotroHttpRequestHandler extends SimpleChannelUpstreamHandler { + private Logger _log = Logger.getLogger(LotroHttpRequestHandler.class); + + private Map _objects; + private UriRequestHandler _uriRequestHandler; + + public LotroHttpRequestHandler(Map objects, UriRequestHandler uriRequestHandler) { + _objects = objects; + _uriRequestHandler = uriRequestHandler; + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, final MessageEvent e) throws Exception { + final HttpRequest request = (HttpRequest) e.getMessage(); + + if (is100ContinueExpected(request)) { + send100Continue(e); + } + + String uri = request.getUri(); + + if (uri.indexOf("?") > -1) + uri = uri.substring(0, uri.indexOf("?")); + + if (request.isChunked()) { + send400Error(request, e); + } else { + ResponseWriter responseWriter = new ResponseWriter() { + @Override + public void writeError(int status) { + writeHttpErrorResponse(request, status, e); + } + + @Override + public void writeResponse(Document document) { + writeHttpResponse(request, document, null, e); + } + + @Override + public void writeResponse(Document document, Map addCookies) { + writeHttpResponse(request, document, addCookies, e); + } + + @Override + public void writeHtmlResponse(String html) { + writeHttpHtmlResponse(request, html, e); + } + + @Override + public void writeFile(File file) { + writeFileResponse(request, file, e); + } + }; + + _uriRequestHandler.handleRequest(uri, request, _objects, responseWriter, e); + } + } + + private void writeFileResponse(HttpRequest request, File file, MessageEvent e) { + try { + RandomAccessFile raf; + try { + raf = new RandomAccessFile(file, "r"); + } catch (FileNotFoundException fnfe) { + writeHttpErrorResponse(request, 404, e); + return; + } + long fileLength = raf.length(); + + + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); + response.setHeader(CONTENT_LENGTH, fileLength); + + String fileName = file.getName(); + if (fileName.endsWith(".html")) { + response.setHeader(CACHE_CONTROL, "no-cache"); + response.setHeader(PRAGMA, "no-cache"); + response.setHeader(EXPIRES, -1); + response.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8"); + } + + Channel ch = e.getChannel(); + + // Write the initial line and the header. + ch.write(response); + + // Write the content. + ChannelFuture writeFuture; + // No encryption - use zero-copy. + final FileRegion region = + new DefaultFileRegion(raf.getChannel(), 0, fileLength); + writeFuture = ch.write(region); + writeFuture.addListener(new ChannelFutureProgressListener() { + public void operationComplete(ChannelFuture future) { + region.releaseExternalResources(); + } + + public void operationProgressed( + ChannelFuture future, long amount, long current, long total) { + } + }); + + // Decide whether to close the connection or not. + if (!isKeepAlive(request)) { + // Close the connection when the whole content is written out. + writeFuture.addListener(ChannelFutureListener.CLOSE); + } + } catch (IOException exp) { + writeHttpErrorResponse(request, 500, e); + } + } + + private void writeHttpErrorResponse(HttpRequest request, int status, MessageEvent e) { + boolean keepAlive = isKeepAlive(request); + + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(status)); + + if (keepAlive) { + // Add 'Content-Length' header only for a keep-alive connection. + response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes()); + } + + ChannelFuture future = e.getChannel().write(response); + if (!keepAlive) { + future.addListener(ChannelFutureListener.CLOSE); + } + } + + private void writeHttpResponse(HttpRequest request, Document document, Map addCookies, MessageEvent e) { + // Decide whether to close the connection or not. + boolean keepAlive = isKeepAlive(request); + + try { + // Build the response object. + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); + + if (addCookies != null) { + CookieEncoder cookieEncoder = new CookieEncoder(true); + for (Map.Entry cookie : addCookies.entrySet()) + cookieEncoder.addCookie(cookie.getKey(), cookie.getValue()); + response.setHeader(SET_COOKIE, cookieEncoder.encode()); + } + + int length = 0; + if (document != null) { + DOMSource domSource = new DOMSource(document); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.transform(domSource, result); + + String responseString = writer.toString(); + response.setContent(ChannelBuffers.copiedBuffer(responseString, CharsetUtil.UTF_8)); + response.setHeader(CONTENT_TYPE, "application/xml; charset=UTF-8"); + + length = responseString.length(); + } + + if (keepAlive) { + // Add 'Content-Length' header only for a keep-alive connection. + response.setHeader(CONTENT_LENGTH, length); + } + + // Write the response. + ChannelFuture future = e.getChannel().write(response); + if (!keepAlive) { + future.addListener(ChannelFutureListener.CLOSE); + } + } catch (Exception exp) { + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(500)); + + if (keepAlive) { + // Add 'Content-Length' header only for a keep-alive connection. + response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes()); + } + + // Write the response. + ChannelFuture future = e.getChannel().write(response); + if (!keepAlive) { + future.addListener(ChannelFutureListener.CLOSE); + } + } + + } + + private void writeHttpHtmlResponse(HttpRequest request, String html, MessageEvent e) { + // Decide whether to close the connection or not. + boolean keepAlive = isKeepAlive(request); + + try { + // Build the response object. + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); + + response.setContent(ChannelBuffers.copiedBuffer(html, CharsetUtil.UTF_8)); + response.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8"); + + int length = html.length(); + + if (keepAlive) { + // Add 'Content-Length' header only for a keep-alive connection. + response.setHeader(CONTENT_LENGTH, length); + } + + // Write the response. + ChannelFuture future = e.getChannel().write(response); + if (!keepAlive) { + future.addListener(ChannelFutureListener.CLOSE); + } + } catch (Exception exp) { + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(500)); + + if (keepAlive) { + // Add 'Content-Length' header only for a keep-alive connection. + response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes()); + } + + // Write the response. + ChannelFuture future = e.getChannel().write(response); + if (!keepAlive) { + future.addListener(ChannelFutureListener.CLOSE); + } + } + } + + private void send400Error(HttpRequest request, MessageEvent e) { + boolean keepAlive = isKeepAlive(request); + + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, BAD_REQUEST); + ChannelFuture future = e.getChannel().write(response); + if (!keepAlive) { + future.addListener(ChannelFutureListener.CLOSE); + } + } + + private void send100Continue(MessageEvent e) { + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, CONTINUE); + e.getChannel().write(response); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + _log.error("Error while processing request", e.getCause()); + e.getChannel().close(); + } +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HttpServerPipelineFactory.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroServerPipelineFactory.java similarity index 77% rename from gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HttpServerPipelineFactory.java rename to gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroServerPipelineFactory.java index e5f23336d..19c48144b 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/HttpServerPipelineFactory.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroServerPipelineFactory.java @@ -15,7 +15,23 @@ import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; -public class HttpServerPipelineFactory implements ChannelPipelineFactory { +public class LotroServerPipelineFactory implements ChannelPipelineFactory { + private Map _context; + private UriRequestHandler _uriRequestHandler; + + public LotroServerPipelineFactory() { + Map objects = new HashMap(); + objects.put(PacksStorage.class, PacksStorageBuilder.createPacksStorage()); + DaoBuilder.fillObjectMap(objects); + ServerBuilder.fillObjectMap(objects); + ServerBuilder.constructObjects(objects); + + objects.put(LoggedUserHolder.class, new LoggedUserHolder()); + + _context = objects; + _uriRequestHandler = new RootUriRequestHandler(_context); + } + public ChannelPipeline getPipeline() throws Exception { // Create a default pipeline implementation. ChannelPipeline pipeline = Channels.pipeline(); @@ -32,13 +48,7 @@ public class HttpServerPipelineFactory implements ChannelPipelineFactory { // Remove the following line if you don't want automatic content compression. pipeline.addLast("deflater", new HttpContentCompressor()); - Map objects = new HashMap(); - objects.put(PacksStorage.class, PacksStorageBuilder.createPacksStorage()); - DaoBuilder.fillObjectMap(objects); - ServerBuilder.fillObjectMap(objects); - ServerBuilder.constructObjects(objects); - - pipeline.addLast("handler", new HttpRequestHandler(objects)); + pipeline.addLast("handler", new LotroHttpRequestHandler(_context, _uriRequestHandler)); return pipeline; } } diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroServerRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroServerRequestHandler.java new file mode 100644 index 000000000..f42e63f85 --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/LotroServerRequestHandler.java @@ -0,0 +1,74 @@ +package com.gempukku.lotro.async; + +import com.gempukku.lotro.db.PlayerDAO; +import com.gempukku.lotro.game.Player; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.QueryStringDecoder; +import org.jboss.netty.handler.codec.http.multipart.Attribute; +import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder; +import org.jboss.netty.handler.codec.http.multipart.InterfaceHttpData; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +public class LotroServerRequestHandler { + protected PlayerDAO _playerDao; + protected LoggedUserHolder _loggedUserHolder; + + public LotroServerRequestHandler(Map context) { + _playerDao = extractObject(context, PlayerDAO.class); + _loggedUserHolder = extractObject(context, LoggedUserHolder.class); + } + + private boolean isTest() { + return Boolean.valueOf(System.getProperty("test")); + } + + protected final Player getResourceOwnerSafely(HttpRequest request, String participantId) throws HttpProcessingException { + String loggedUser = _loggedUserHolder.getLoggedUser(request); + if (isTest() && loggedUser == null) + loggedUser = participantId; + + if (loggedUser == null) + throw new HttpProcessingException(401); + + Player resourceOwner = _playerDao.getPlayer(loggedUser); + + if (resourceOwner == null) + throw new HttpProcessingException(401); + + if (resourceOwner.getType().contains("a") && participantId != null && !participantId.equals("null") && !participantId.equals("")) { + resourceOwner = _playerDao.getPlayer(participantId); + if (resourceOwner == null) + throw new HttpProcessingException(401); + } + return resourceOwner; + } + + protected String getQueryParameterSafely(QueryStringDecoder queryStringDecoder, String parameterName) { + List parameterValues = queryStringDecoder.getParameters().get(parameterName); + if (parameterValues.size() > 0) + return parameterValues.get(0); + else + return null; + } + + protected String getFormParameterSafely(HttpPostRequestDecoder postRequestDecoder, String parameterName) throws IOException, HttpPostRequestDecoder.NotEnoughDataDecoderException { + InterfaceHttpData data = postRequestDecoder.getBodyHttpData(parameterName); + if (data == null) + return null; + if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) { + Attribute attribute = (Attribute) data; + return attribute.getValue(); + } else { + return null; + } + } + + protected T extractObject(Map context, Class clazz) { + Object value = context.get(clazz); + return (T) value; + } +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/ResponseWriter.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/ResponseWriter.java new file mode 100644 index 000000000..c4715e7e2 --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/ResponseWriter.java @@ -0,0 +1,18 @@ +package com.gempukku.lotro.async; + +import org.w3c.dom.Document; + +import java.io.File; +import java.util.Map; + +public interface ResponseWriter { + public void writeError(int status); + + public void writeFile(File file); + + public void writeHtmlResponse(String html); + + public void writeResponse(Document document); + + public void writeResponse(Document document, Map addCookies); +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/RootUriRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/RootUriRequestHandler.java new file mode 100644 index 000000000..746c489f4 --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/RootUriRequestHandler.java @@ -0,0 +1,37 @@ +package com.gempukku.lotro.async; + +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.handler.codec.http.HttpRequest; + +import java.lang.reflect.Type; +import java.util.Map; + +public class RootUriRequestHandler implements UriRequestHandler { + private String _serverContextPath ="/gemp-lotr-server/"; + private String _webContextPath="/gemp-lotr/"; + private HallRequestHandler _hallRequestHandler; + private WebRequestHandler _webRequestHandler = new WebRequestHandler(); + private LoginRequestHandler _loginRequestHandler; + private StatusRequestHandler _statusRequestHandler; + + public RootUriRequestHandler(Map context) { + _hallRequestHandler = new HallRequestHandler(context); + _loginRequestHandler = new LoginRequestHandler(context); + _statusRequestHandler = new StatusRequestHandler(context); + } + + @Override + public void handleRequest(String uri, HttpRequest request, Map context, ResponseWriter responseWriter, MessageEvent e) { + if (uri.startsWith(_webContextPath)) { + _webRequestHandler.handleRequest(uri.substring(_webContextPath.length()), request, context, responseWriter, e); + } else if (uri.startsWith(_serverContextPath +"hall")) { + _hallRequestHandler.handleRequest(uri.substring(_serverContextPath.length()+4), request, context, responseWriter, e); + } else if (uri.startsWith(_serverContextPath+"login")) { + _loginRequestHandler.handleRequest(uri.substring(_serverContextPath.length()+5), request, context, responseWriter, e); + } else if (uri.equals(_serverContextPath)) { + _statusRequestHandler.handleRequest(uri.substring(_serverContextPath.length()), request, context, responseWriter, e); + } else { + responseWriter.writeError(404); + } + } +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/StatusRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/StatusRequestHandler.java new file mode 100644 index 000000000..e0f7cebe6 --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/StatusRequestHandler.java @@ -0,0 +1,41 @@ +package com.gempukku.lotro.async; + +import com.gempukku.lotro.chat.ChatServer; +import com.gempukku.lotro.game.GameHistoryService; +import com.gempukku.lotro.hall.HallServer; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.handler.codec.http.HttpMethod; +import org.jboss.netty.handler.codec.http.HttpRequest; + +import java.lang.reflect.Type; +import java.util.Map; + +public class StatusRequestHandler extends LotroServerRequestHandler implements UriRequestHandler { + private HallServer _hallServer; + private GameHistoryService _gameHistoryService; + private ChatServer _chatServer; + + public StatusRequestHandler(Map context) { + super(context); + _hallServer = extractObject(context, HallServer.class); + _gameHistoryService = extractObject(context, GameHistoryService.class); + _chatServer = extractObject(context, ChatServer.class); + } + + @Override + public void handleRequest(String uri, HttpRequest request, Map context, ResponseWriter responseWriter, MessageEvent e) { + if (uri.equals("") && request.getMethod() == HttpMethod.GET) { + StringBuilder sb = new StringBuilder(); + + int day = 1000 * 60 * 60 * 24; + int week = 1000 * 60 * 60 * 24 * 7; + sb.append("Tables count: ").append(_hallServer.getTablesCount()).append(", players in hall: ").append(_chatServer.getChatRoom("Game Hall").getUsersInRoom().size()) + .append(", games played in last 24 hours: ").append(_gameHistoryService.getGamesPlayedCount(System.currentTimeMillis() - day, day)) + .append(",
active players in last week: ").append(_gameHistoryService.getActivePlayersCount(System.currentTimeMillis() - week, week)); + + responseWriter.writeHtmlResponse(sb.toString()); + } else { + responseWriter.writeError(404); + } + } +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/UriRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/UriRequestHandler.java new file mode 100644 index 000000000..b96b3710e --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/UriRequestHandler.java @@ -0,0 +1,11 @@ +package com.gempukku.lotro.async; + +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.handler.codec.http.HttpRequest; + +import java.lang.reflect.Type; +import java.util.Map; + +public interface UriRequestHandler { + public void handleRequest(String uri, HttpRequest request, Map context, ResponseWriter responseWriter, MessageEvent e); +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/WebRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/WebRequestHandler.java new file mode 100644 index 000000000..904658845 --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/WebRequestHandler.java @@ -0,0 +1,29 @@ +package com.gempukku.lotro.async; + +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.handler.codec.http.HttpRequest; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.Map; + +public class WebRequestHandler implements UriRequestHandler { + private String _root = "I:\\dev\\projects\\gemp-lotr\\gemp-lotr-web\\src\\main\\webapp\\"; + + @Override + public void handleRequest(String uri, HttpRequest request, Map context, ResponseWriter responseWriter, MessageEvent e) { + if (uri.equals("")) + uri = "index.html"; + + uri = uri.replace('/', File.separatorChar); + + if ((uri.contains("..")) + || uri.contains(File.separator + ".") + || uri.startsWith(".") || uri.endsWith(".")) { + responseWriter.writeError(403); + } else { + File file = new File(_root + uri); + responseWriter.writeFile(file); + } + } +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/resources/test-log4j.xml b/gemp-lotr/gemp-lotr-async/src/main/resources/test-log4j.xml new file mode 100644 index 000000000..662d114f0 --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/resources/test-log4j.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/collection/CollectionsManager.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/collection/CollectionsManager.java index 5e8f7fce1..a16d0eabc 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/collection/CollectionsManager.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/collection/CollectionsManager.java @@ -34,9 +34,11 @@ public class CollectionsManager { // Hunters have 1-194 normal cards, 9 "O" cards, and 3 extra to cover the different culture versions of 15_60 + boolean test = Boolean.valueOf(System.getProperty("test")); + final int[] cardCounts = new int[]{129, 365, 122, 122, 365, 128, 128, 365, 122, 52, 122, 266, 203, 203, 15, 207, 6, 157, 149, 40}; - for (int i = 0; i <= 19; i++) { + for (int i = 0; i <= (test?1:19); i++) { _logger.debug("Loading set " + i); for (int j = 1; j <= cardCounts[i]; j++) { String blueprintId = i + "_" + j;