WebPreferencesService.java 10.9 KB
package com.mumfrey.webprefs.framework;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import net.minecraft.util.Session;

import org.apache.commons.io.IOUtils;

import com.google.common.base.Charsets;
import com.google.gson.Gson;
import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
import com.mumfrey.webprefs.exceptions.InvalidRequestException;
import com.mumfrey.webprefs.exceptions.InvalidResponseException;
import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest;
import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse;
import com.mumfrey.webprefs.interfaces.IWebPreferencesService;
import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceMonitor;

class WebPreferencesService implements IWebPreferencesService
{
    private static final int TIMEOUT_MSEC = 5000;

    private final Proxy proxy;
    
    private final Session session;

    private final Map<String, String> serverKeys = new HashMap<String, String>();

    private final List<IWebPreferencesServiceMonitor> monitors = new ArrayList<IWebPreferencesServiceMonitor>();

    private long lastMojangAuth = 0L;

    WebPreferencesService(Proxy proxy, Session session)
    {
        this.proxy = proxy;
        this.session = session;
    }

    @Override
    public void addMonitor(IWebPreferencesServiceMonitor monitor)
    {
        if (!this.monitors.contains(monitor))
        {
            this.monitors.add(monitor);
        }
    }
    
    void handleKeyRequestFailed(Throwable th)
    {
        LiteLoaderLogger.debug(th, "Key request failed with message %s", th.getMessage());

        for (IWebPreferencesServiceMonitor monitor : this.monitors)
        {
            monitor.onKeyRequestFailed();
        }
    }

    void handleKeyRequestCompleted(IWebPreferencesResponse response)
    {
    }

    @Override
    public void submit(IWebPreferencesRequest request)
    {
        try
        {
            this.beginProcessingRequest(request);
        }
        catch (InvalidRequestException ex)
        {
            request.getDelegate().onRequestFailed(request, ex, ex.getReason());
        }
    }
    
    private IWebPreferencesResponse beginProcessingRequest(IWebPreferencesRequest request) throws InvalidRequestException
    {
        LiteLoaderLogger.debug("WebPreferencesService is processing %s for %s", request.getClass().getSimpleName(), request.getUUID());
        
        if (request.isValidationRequired())
        {
            String requestClass = request.getClass().getSimpleName();

            Session session = request.getDelegate().getSession();
            if (session == null)
            {
                throw new InvalidRequestException(RequestFailureReason.NO_SESSION,
                        "Validation is required for " + requestClass + " but no session was provided.");
            }
            
            String serverId = this.getServerIdForRequest(request);

            if (!this.registerServerConnection(session, serverId))
            {
                throw new InvalidRequestException(RequestFailureReason.NO_SESSION,
                        "Validation is required for " + requestClass + " but no session was provided or session validation failed");
            }
        }

        return this.processRequest(request);
    }
    
    private IWebPreferencesResponse processRequest(IWebPreferencesRequest request)
    {
        try
        {
            String data = this.httpPost(request.getRequestURI(), request.getPostVars());
            IWebPreferencesResponse response = WebPreferencesResponse.fromJson(data);
            
            LiteLoaderLogger.debug("Response: %s", response);
            request.onReceivedResponse(response);

            request.getDelegate().onReceivedResponse(request, response);
            return response;
        }
        catch (InvalidResponseException ex)
        {
            request.getDelegate().onRequestFailed(request, ex, ex.getReason());

            for (IWebPreferencesServiceMonitor monitor : this.monitors)
            {
                monitor.onRequestFailed(ex, ex.getReason().getSeverity());
            }
        }
        catch (IOException ex)
        {
            request.getDelegate().onRequestFailed(request, ex, RequestFailureReason.SERVER_ERROR);

            for (IWebPreferencesServiceMonitor monitor : this.monitors)
            {
                monitor.onRequestFailed(ex, RequestFailureReason.SERVER_ERROR.getSeverity());
            }
        }
        catch (Exception ex)
        {
            for (IWebPreferencesServiceMonitor monitor : this.monitors)
            {
                monitor.onRequestFailed(ex, RequestFailureReason.UNKNOWN.getSeverity());
            }
        }

        return null;
    }

    private String getServerIdForRequest(IWebPreferencesRequest request)
    {
        if (request.getDelegate().getSession() == null)
        {
            return null;
        }

        String hostName = request.getDelegate().getHostName();
        String serverId = this.serverKeys.get(hostName);

        if (serverId == null)
        {
            LiteLoaderLogger.info("Looking up server ID for " + hostName);
            WebPreferencesRequestKey keyRequest = new WebPreferencesRequestKey(this, this.session, hostName);
            IWebPreferencesResponse response = this.processRequest(keyRequest);
            if (response == null || response.getServerId() == null)
            {
                throw new InvalidRequestException(RequestFailureReason.SERVER_ERROR, "Could not retrieve server ID for " + hostName);
            }

            serverId = response.getServerId();
            this.serverKeys.put(hostName, serverId);

            LiteLoaderLogger.info("Got server ID for " + hostName + " [" + serverId + "]");
        }

        return serverId;
    }
    
    public String httpPost(URI uri, Map<String, String> params) throws IOException
    {
        String query = this.buildQuery(params);
        byte[] queryBytes = query.getBytes(Charsets.UTF_8);
        
        LiteLoaderLogger.debug("Connecting to " + uri);
        HttpURLConnection http = (HttpURLConnection)uri.toURL().openConnection(this.proxy);
        http.setConnectTimeout(WebPreferencesService.TIMEOUT_MSEC);
        http.setReadTimeout(WebPreferencesService.TIMEOUT_MSEC);
        http.setUseCaches(false);
        http.setDoOutput(true);

        http.addRequestProperty("Content-type", "application/x-www-form-urlencoded");
        http.setRequestProperty("Content-Length", "" + queryBytes.length);

        OutputStream outputStream = null;

        try
        {
            outputStream = http.getOutputStream();
            IOUtils.write(queryBytes, outputStream);
        }
        finally
        {
            IOUtils.closeQuietly(outputStream);
        }

        try
        {
            String debugMessages = http.getHeaderField("X-Debug-Message");
            if (debugMessages != null)
            {
                String[] messages = new Gson().fromJson(debugMessages, String[].class);
                for (String message : messages)
                {
                    LiteLoaderLogger.debug("[SERVER] %s", message);
                }
            }
        }
        catch (Exception ex) {}

        InputStream inputStream = null;

        try
        {
            try
            {
                inputStream = http.getInputStream();
                String response = IOUtils.toString(inputStream, Charsets.UTF_8);
                return response;
            }
            catch (IOException ex)
            {
                IOUtils.closeQuietly(inputStream);
                inputStream = http.getErrorStream();
                if (inputStream == null)
                {
                    return this.formatErrorAsJson(http.getResponseCode() + " " + http.getResponseMessage(), ex.getMessage());
                }

                String response = IOUtils.toString(inputStream, Charsets.UTF_8);

                String contentType = http.getHeaderField("Content-type");
                if (!"application/json".equals(contentType))
                {
                    System.err.println(response);
                    return this.formatErrorAsJson(http.getResponseCode() + " " + http.getResponseMessage(), "Invalid content type " + contentType);
                }

                return response;
            }
        }
        finally
        {
            IOUtils.closeQuietly(inputStream);
        }
    }
    
    private String formatErrorAsJson(String response, String message)
    {
        return String.format("{\"response\":\"%s\",\"message\":\"%s\"}", response, message);
    }
    
    private String buildQuery(Map<String, String> params)
    {
        StringBuilder sb = new StringBuilder();
        
        try
        {
            String separator = "";
            for (Entry<String, String> postValue : params.entrySet())
            {
                sb.append(separator).append(postValue.getKey()).append("=").append(URLEncoder.encode(postValue.getValue(), "UTF-8"));
                separator = "&";
            }
        }
        catch (UnsupportedEncodingException ex)
        {
            ex.printStackTrace();
        }

        return sb.toString();
    }
    
    private boolean registerServerConnection(Session session, String serverId)
    {
        if (session == null || serverId == null)
        {
            return false;
        }

        if (System.currentTimeMillis() - this.lastMojangAuth < 300000L)
        {
            LiteLoaderLogger.debug("Mojang connection is still fresh, using existing ticket");
            return true;
        }

        try
        {
            LiteLoaderLogger.debug("Creating Mojang session ticket...");
            URL checkServerUrl = new URL("http://session.minecraft.net/game/joinserver.jsp?user=" + URLEncoder.encode(session.getUsername(), "UTF-8") + "&sessionId=" + URLEncoder.encode(session.getSessionID(), "UTF-8") + "&serverId=" + URLEncoder.encode(serverId, "UTF-8"));
            BufferedReader responseReader = new BufferedReader(new InputStreamReader(checkServerUrl.openStream()));
            String response = responseReader.readLine();
            responseReader.close();
            boolean joinSuccess = "OK".equals(response);
            if (joinSuccess)
            {
                this.lastMojangAuth = System.currentTimeMillis();
                return true;
            }
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
            LiteLoaderLogger.debug("Failed to log on to invoke joinserver, connection to mojang failed");
            throw new InvalidRequestException(RequestFailureReason.SERVER_ERROR, "Failed registering server connection with Mojang");
        }

        return false;
    }
}