Question

I'm writing a very basic HTTP server in Java to serve files (resource packs) for the game Minecraft. Resource packs are distributed in the ZIP file format. The original file is zipped into an archive like so:

public void createZIPFile(File file) throws IOException{
    File output = new File(file.getParentFile(), file.getName() + ".zip");
    ZipOutputStream out = new ZipOutputStream(new FileOutputStream(output));

    ZipEntry entry = new ZipEntry("assets/minecraft/sounds/mob/wolf/howl.ogg");
    out.putNextEntry(entry);
    out.write(Files.readAllBytes(file.toPath()));
    out.closeEntry();

    ZipEntry mcMeta = new ZipEntry("pack.mcmeta");
    out.putNextEntry(mcMeta);
    out.write(("{\r\n" + "    \"pack\": {\r\n" + "         \"pack_format\": 1,\r\n" + "         \"description\": \"SoundCraft!\"\r\n" + "     }\r\n" + "}").getBytes());
    out.closeEntry();

    out.finish();
    out.flush();
    out.close();
}

With the introduction of the new Channels API in JRE7, I decided to use that instead of the traditional while-read loop. The HTTP server class is as follows:

package priv.tenko.soundcraft.http;

import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

import priv.tenko.soundcraft.SoundCraft;

public class BasicHTTPServer extends Thread {

    private final Logger httpLog = Logger.getLogger("SoundCraft-HTTP");
    private final int port = 25566;
    private AtomicBoolean running = new AtomicBoolean(false);
    private ServerSocketChannel socket;

    public void close(){
        running.set(false);
        try{
            socket.close();
        }catch (IOException e){
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    public void run(){
        socket = null;
        running.set(true);

        try{
            httpLog.info("Starting basic HTTP web server on port " + port + "...");
            socket = ServerSocketChannel.open();
            socket.bind(new InetSocketAddress("localhost", port));
            httpLog.fine("Successfully binded! Awaiting requests.");
        }catch (IOException e){
            e.printStackTrace();
        }

        while(running.get()){
            try{
                SocketChannel newConnection = socket.accept();
                newConnection.configureBlocking(true);

                InetSocketAddress address = (InetSocketAddress) newConnection.getRemoteAddress();

                ByteBuffer read = ByteBuffer.allocate(128);
                newConnection.read(read);
                String request = new String(read.compact().array());

                if(!request.startsWith("GET")){
                    httpLog.warning("Recieved non-GET HTTP request. Please look into it? " + address.getAddress().getHostAddress() + ":" + address.getPort() + " tried " + request);
                    newConnection.close();
                    continue;
                }

                request = request.substring(5); // At this point, it should be "somefile.ext HTTP/1.1" or whatever
                request = request.substring(0, request.indexOf(' ')); // Aaaaand now it should be "somefile.ext"
                httpLog.info(newConnection.getRemoteAddress() + " is requesting " + request);
                httpLog.info("Outside URL would look like: " + this.getURLDestination(request));
                File target = new File(SoundCraft.getSoundDirectory(), request);
                if(!target.exists()){
                    httpLog.info("Couldn't find the requested file. Terminating connection.");
                    newConnection.close();
                    continue;
                }

                newConnection.write(ByteBuffer.wrap(getOkayMessage(target).getBytes()));

                try(FileInputStream stream = new FileInputStream(target);){
                    stream.getChannel().transferTo(0, target.length(), newConnection);
                }

                newConnection.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }

        try{
            socket.close();
        }catch (Throwable e){
            e.printStackTrace();
        }
    }

    public String getURLDestination(String file){
        try{
            return "http://localhost:" + port + "/" + file;
        }catch (Exception e){
            e.printStackTrace();
            return "";
        }
    }

    public String getOkayMessage(File file){
        return "HTTP/1.0 200 OK\r\n" + "Content-Length: " + file.length() + "\r\n" + "Connection: close\r\n" + "Server: SoundCraft-HTTP\r\n" + "Content-Type: text/html\r\n" + "Content-Transfer-Encoding: BINARY\r\n";
    }
}

However, when I use this method, and the client downloads the ZIP file, the ZIP is partially corrupted. Further inspection by opening the ZIP file in Notepad++ showed that the server is skipping part of the file.

I cannot post images, so this is a direct link to the image which refers to the data-skipping. This question is closely related to this one; we both are encountering an issue involving missing data. However, the related question uses the traditional while-read loop, while mine uses the new Channels API.

Any help is appreciated!

Was it helpful?

Solution

transferTo() and transferFrom() must be called in a loop until they stop returning positive values. They're not specified to perform the entire transform in a single operation. You need to adjust the offset by the previous return value each time.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top