All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.splout.db.dnode.HttpFileExchanger Maven / Gradle / Ivy

Go to download

Splout SQL is a read only, horizontally scalable and partitioned SQL database that plays well with Hadoop.

There is a newer version: 0.3.0
Show newest version
package com.splout.db.dnode;

/*
 * #%L
 * Splout SQL Server
 * %%
 * Copyright (C) 2012 - 2013 Datasalt Systems S.L.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 * #L%
 */

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.BindException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.splout.db.common.SploutConfiguration;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

/**
 * A simple class that allows for fast (GZip), chunked transport of binary files between nodes through HTTP. This class
 * is both a server and a client: use its Runnable method for creating a server that can receive files or use the
 * {@link #send(File, String)} method for sending a file to another peer.
 * 

* For safety, every transfer checks whether the checksum (CRC32) matches the expected one or not. *

* This class should have the same semantics as {@link Fetcher} so it should save files to the same temp folder, etc. It * can be configured by {@link SploutConfiguration}. */ public class HttpFileExchanger extends Thread implements HttpHandler { private final static Log log = LogFactory.getLog(HttpFileExchanger.class); private File tempDir; private SploutConfiguration config; // this thread pool is passed to the HTTP server for handling incoming requests private ExecutorService serverExecutors; // this thread pool is used for sending multiple files at the same time private ExecutorService clientExecutors; private HttpServer server; private AtomicBoolean isListening = new AtomicBoolean(false); private AtomicBoolean isInit = new AtomicBoolean(false); // This callback will be called when the files are received private ReceiveFileCallback callback; private Map currentTransfers = new HashMap(); private Object currentTransfersMonitor = new Object(); public HttpFileExchanger(SploutConfiguration config, ReceiveFileCallback callback) { this.config = config; this.callback = callback; } public interface ReceiveFileCallback { public void onProgress(String tablespace, Integer partition, Long version, File file, long totalSize, long sizeDownloaded); public void onFileReceived(String tablespace, Integer partition, Long version, File file); public void onBadCRC(String tablespace, Integer partition, Long version, File file); public void onError(Throwable t, String tablespace, Integer partition, Long version, File file); } /** * We initialize everything in an init() method to be able to catch explicit exceptions (otherwise that's not possible * in Thread's run()). */ public void init() throws IOException { tempDir = new File(config.getString(FetcherProperties.TEMP_DIR)); int httpPort = 0; int trials = 0; boolean bind = false; do { try { httpPort = config.getInt(HttpFileExchangerProperties.HTTP_PORT); server = HttpServer.create(new InetSocketAddress(config.getString(DNodeProperties.HOST), httpPort), config.getInt(HttpFileExchangerProperties.HTTP_BACKLOG)); bind = true; } catch(BindException e) { if(config.getBoolean(HttpFileExchangerProperties.HTTP_PORT_AUTO_INCREMENT)) { config.setProperty(HttpFileExchangerProperties.HTTP_PORT, httpPort + 1); } else { throw e; } } } while(!bind && trials < 50); // serve all http requests at root context server.createContext("/", this); serverExecutors = Executors.newFixedThreadPool(config .getInt(HttpFileExchangerProperties.HTTP_THREADS_SERVER)); clientExecutors = Executors.newFixedThreadPool(config .getInt(HttpFileExchangerProperties.HTTP_THREADS_CLIENT)); server.setExecutor(serverExecutors); isInit.set(true); } public String address() { return "http://" + config.getString(DNodeProperties.HOST) + ":" + config.getInt(HttpFileExchangerProperties.HTTP_PORT); } @Override public void run() { if(!isInit.get()) { throw new IllegalStateException("HTTP server must be init with init() method."); } server.start(); log.info("HTTP File exchanger LISTENING on port: " + config.getInt(HttpFileExchangerProperties.HTTP_PORT)); isListening.set(true); } public boolean isListening() { return isListening.get(); } public void close() { if(server != null) { server.stop(1); serverExecutors.shutdown(); clientExecutors.shutdown(); log.warn("HTTP File exchanger STOPPED."); } } @Override public void handle(HttpExchange exchange) throws IOException { DataInputStream iS = null; FileOutputStream writer = null; File dest = null; String tablespace = null; Integer partition = null; Long version = null; try { iS = new DataInputStream(new GZIPInputStream(exchange.getRequestBody())); String fileName = exchange.getRequestHeaders().getFirst("filename"); tablespace = exchange.getRequestHeaders().getFirst("tablespace"); partition = Integer.valueOf(exchange.getRequestHeaders().getFirst("partition")); version = Long.valueOf(exchange.getRequestHeaders().getFirst("version")); dest = new File(new File(tempDir, DNodeHandler.getLocalStoragePartitionRelativePath(tablespace, partition, version)), fileName); // just in case, avoid copying the same file concurrently // (but we also shouldn't avoid this in other levels of the app) synchronized(currentTransfersMonitor) { if(currentTransfers.containsKey(dest.toString())) { throw new IOException("Incoming file already being transferred - " + dest); } currentTransfers.put(dest.toString(), new Object()); } if(!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } if(dest.exists()) { dest.delete(); } writer = new FileOutputStream(dest); byte[] buffer = new byte[config.getInt(FetcherProperties.DOWNLOAD_BUFFER)]; Checksum checkSum = new CRC32(); // 1- Read file size long fileSize = iS.readLong(); log.debug("Going to read file [" + fileName + "] of size: " + fileSize); // 2- Read file contents long readSoFar = 0; do { long missingBytes = fileSize - readSoFar; int bytesToRead = (int) Math.min(missingBytes, buffer.length); int read = iS.read(buffer, 0, bytesToRead); checkSum.update(buffer, 0, read); writer.write(buffer, 0, read); readSoFar += read; callback.onProgress(tablespace, partition, version, dest, fileSize, readSoFar); } while(readSoFar < fileSize); // 3- Read CRC long expectedCrc = iS.readLong(); if(expectedCrc == checkSum.getValue()) { log.info("File [" + dest.getAbsolutePath() + "] received -> Checksum -- " + checkSum.getValue() + " matches expected CRC [OK]"); callback.onFileReceived(tablespace, partition, version, dest); } else { log.error("File received [" + dest.getAbsolutePath() + "] -> Checksum -- " + checkSum.getValue() + " doesn't match expected CRC: " + expectedCrc); callback.onBadCRC(tablespace, partition, version, dest); dest.delete(); } } catch(Throwable t) { log.error(t); callback.onError(t, tablespace, partition, version, dest); if(dest != null && dest.exists() && !t.getMessage().contains("Incoming file already being transferred")) { dest.delete(); } } finally { if(writer != null) { writer.close(); } if(iS != null) { iS.close(); } if(dest != null) { currentTransfers.remove(dest.toString()); } } } public void send(final String tablespace, final int partition, final long version, final File binaryFile, final String url, boolean blockUntilComplete) { Future future = clientExecutors.submit(new Runnable() { @Override public void run() { DataOutputStream writer = null; InputStream input = null; try { HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); connection.setChunkedStreamingMode(config.getInt(FetcherProperties.DOWNLOAD_BUFFER)); connection.setDoOutput(true); connection.setRequestProperty("filename", binaryFile.getName()); connection.setRequestProperty("tablespace", tablespace); connection.setRequestProperty("partition", partition + ""); connection.setRequestProperty("version", version + ""); Checksum checkSum = new CRC32(); writer = new DataOutputStream(new GZIPOutputStream(connection.getOutputStream())); // 1 - write file size writer.writeLong(binaryFile.length()); writer.flush(); // 2 - write file content input = new FileInputStream(binaryFile); byte[] buffer = new byte[config.getInt(FetcherProperties.DOWNLOAD_BUFFER)]; long wrote = 0; for(int length = 0; (length = input.read(buffer)) > 0;) { writer.write(buffer, 0, length); checkSum.update(buffer, 0, length); wrote += length; } // 3 - add the CRC so that we can verify the download writer.writeLong(checkSum.getValue()); writer.flush(); log.info("Sent file " + binaryFile + " to " + url + " with #bytes: " + wrote + " and checksum: " + checkSum.getValue()); } catch(IOException e) { log.error(e); } finally { try { if(input != null) { input.close(); } if(writer != null) { writer.close(); } } catch(IOException ignore) { } } } }); try { if(blockUntilComplete) { while(future.isDone() || future.isCancelled()) { Thread.sleep(1000); } } } catch(InterruptedException e) { // interrupted! } } public Map getCurrentTransfers() { return currentTransfers; } // Use this main for testing purposes, as a client // args: [file] [server] public static void main(String[] args) throws IOException { SploutConfiguration conf = SploutConfiguration.get(); HttpFileExchanger fileExchanger = new HttpFileExchanger(conf, new ReceiveFileCallback() { @Override public void onProgress(String tablespace, Integer partition, Long version, File file, long totalSize, long sizeDownloaded) { } @Override public void onFileReceived(String tablespace, Integer partition, Long version, File file) { } @Override public void onError(Throwable t, String tablespace, Integer partition, Long version, File file) { } @Override public void onBadCRC(String tablespace, Integer partition, Long version, File file) { } }); fileExchanger.init(); fileExchanger.send("t1", 0, 1l, new File(args[0]), args[1], true); fileExchanger.close(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy