/* * Copyright 2002-2005 The Apache Software Foundation. * Copyright 2012 James Moger * * 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.moxie.proxy.connection; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.SocketException; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; import org.moxie.Constants; import org.moxie.proxy.LuceneExecutor; import org.moxie.proxy.ProxyConfig; /** * Handle a connection from a maven. * * @author digulla * */ public class ProxyRequestHandler extends Thread { public static final Logger log = Logger.getLogger(ProxyRequestHandler.class.getSimpleName()); private final ProxyConfig config; private final LuceneExecutor lucene; private Socket clientSocket; private enum HttpMethod { GET, HEAD; static HttpMethod fromString(String val) { for (HttpMethod method : values()) { if (val.equals(method.name())) { return method; } } return null; } } public ProxyRequestHandler(ProxyConfig config, LuceneExecutor lucene, Socket clientSocket) { this.config = config; this.lucene = lucene; this.clientSocket = clientSocket; } @Override public void run() { if (clientSocket == null) throw new RuntimeException("Connection is already closed"); try { log.fine("Got connection from " + clientSocket.getInetAddress()); String line; boolean keepAlive = false; do { HttpMethod method = null; String downloadURL = null; StringBuilder fullRequest = new StringBuilder(1024); while ((line = readLine()) != null) { if (line.length() == 0) break; // log.debug ("Got: "+line); fullRequest.append(line); fullRequest.append('\n'); if ("proxy-connection: keep-alive".equals(line.toLowerCase())) keepAlive = true; // parse HTTP method int spc = line.indexOf(' '); if (spc > -1) { HttpMethod m = HttpMethod.fromString(line.substring(0, spc).trim()); if (m != null) { int pos = line.lastIndexOf(' '); line = line.substring(m.name().length(), pos); downloadURL = line; method = m; } } } if (downloadURL == null) { if (line == null) break; log.severe("Found no URL to download in request:\n" + fullRequest.toString()); } else { log.info("Got request for " + method + " " + downloadURL); handle(method, downloadURL); } } while (line != null && keepAlive); log.fine("Terminating connection with " + clientSocket.getInetAddress()); } catch (Exception e) { log.log(Level.SEVERE, "Conversation with client aborted", e); } finally { close(); } } public void close() { try { if (out != null) out.close(); } catch (Exception e) { log.log(Level.SEVERE, "Exception while closing the outputstream", e); } out = null; try { if (in != null) in.close(); } catch (Exception e) { log.log(Level.SEVERE, "Exception while closing the inputstream", e); } in = null; try { if (clientSocket != null) clientSocket.close(); } catch (Exception e) { log.log(Level.SEVERE, "Exception while closing the socket", e); } clientSocket = null; } private void handle(HttpMethod method, String downloadURL) throws IOException { URL url = new URL(downloadURL); url = config.getRedirect(url); if (!"http".equals(url.getProtocol())) throw new IOException("Can only handle HTTP requests, got " + downloadURL); File f = config.getRemoteArtifact(url); if (f == null) { throw new IOException("Unregistered remote repository, got " + downloadURL); } String name = f.getName(); String path = f.getPath().replace('\\', '/'); // ensure we have the requested artifact or the latest version // of the requested artifact if (name.contains("-SNAPSHOT") || name.contains("maven-metadata") || path.contains("/.m2e/") || path.contains("/.meta/") || path.contains("/.nexus/") || !f.exists()) { ProxyDownload d = new ProxyDownload(config, url, f); try { d.download(); // index this artifact's pom if (name.toLowerCase().endsWith(Constants.POM)) { lucene.index(f); } } catch (DownloadFailed e) { log.severe(e.getMessage()); if (!f.exists()) { // return failure println(e.getStatusLine()); println(); getOut().flush(); return; } else { log.fine("Serving from local cache " + f.getAbsolutePath()); } } } else { log.fine("Serving from local cache " + f.getAbsolutePath()); } // now that we have the artifact, handle the client's request switch (method) { case HEAD: handleHEAD(f); break; case GET: handleGET(f); break; default: log.warning("Unimplemented HTTP method " + method); break; } } /** * Set the http headers for the request. * * @param file * @throws IOException */ private void setHeaders(File file) throws IOException { println("HTTP/1.1 200 OK"); println("Server: moxieproxy/" + org.moxie.proxy.Constants.getVersion()); print("Date: "); println(INTERNET_FORMAT.format(new Date(System.currentTimeMillis()))); print("Last-modified: "); println(INTERNET_FORMAT.format(new Date(file.lastModified()))); print("Content-length: "); println(String.valueOf(file.length())); print("Content-type: "); String ext = file.getName().substring(file.getName().lastIndexOf('.') + 1).toLowerCase(); String type = CONTENT_TYPES.get(ext); if (type == null) { log.warning("Unknown extension " + ext + ". Using content type text/plain."); type = "text/plain"; } println(type); println(); } /** * HEAD requests are used to determine the current status of a resource. * * @param file * @throws IOException */ protected void handleHEAD(File file) throws IOException { // set the http headers for the file setHeaders(file); out.flush(); } /** * GET requests are used to retrieve a resource. * * @param file * @throws IOException */ protected void handleGET(File file) throws IOException { // set the http headers for the file setHeaders(file); // load the file for streaming back to the client InputStream data = new BufferedInputStream(new FileInputStream(file)); copy(data, out); data.close(); } void copy(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[1024 * 100]; int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } out.flush(); } public final static HashMap<String, String> CONTENT_TYPES = new HashMap<String, String>(); static { CONTENT_TYPES.put("xml", "text/xml"); CONTENT_TYPES.put("pom", "text/xml"); CONTENT_TYPES.put("jar", "application/java-archive"); CONTENT_TYPES.put("war", "application/java-archive"); CONTENT_TYPES.put("ear", "application/java-archive"); CONTENT_TYPES.put("zip", "application/zip"); CONTENT_TYPES.put("tar", "application/x-tar"); CONTENT_TYPES.put("tgz", "application/gzip"); CONTENT_TYPES.put("gz", "application/gzip"); CONTENT_TYPES.put("txt", "text/plain"); CONTENT_TYPES.put("md5", "text/plain"); CONTENT_TYPES.put("sha1", "text/plain"); CONTENT_TYPES.put("asc", "text/plain"); } private final static SimpleDateFormat INTERNET_FORMAT = new SimpleDateFormat( "EEE, d MMM yyyy HH:mm:ss zzz"); private byte[] NEW_LINE = new byte[] { '\r', '\n' }; private void println(String string) throws IOException { print(string); println(); } private void println() throws IOException { getOut().write(NEW_LINE); } private void print(String string) throws IOException { getOut().write(string.getBytes("ISO-8859-1")); } private OutputStream out; protected OutputStream getOut() throws IOException { if (out == null) out = new BufferedOutputStream(clientSocket.getOutputStream()); return out; } private BufferedInputStream in; private String readLine() throws IOException { if (in == null) in = new BufferedInputStream(clientSocket.getInputStream()); StringBuilder buffer = new StringBuilder(256); int c; try { while ((c = in.read()) != -1) { if (c == '\r') continue; if (c == '\n') break; buffer.append((char) c); } } catch (SocketException e) { if ("connection reset".equals(e.getMessage().toLowerCase())) return null; throw e; } if (c == -1) return null; if (buffer.length() == 0) return ""; return buffer.toString(); } }