Solved Transfering file via HTTP fails; skips the first 24kb (approx)?

Discussion in 'Plugin Development' started by Cirno, May 2, 2014.

Thread Status:
Not open for further replies.
  1. Offline

    Cirno

    I'm currently working on a plugin that runs a small HTTP server to serve resource packs to the client. However, when I attempt to download it, I notice that the file size is slightly smaller. I (stupidly) tried to compare the two by opening them both up in Notepad++ and noticed that the downloaded version starts around 24kb after the original.

    Here's the files via Dropbox (if it helps):
    Downloaded version: https://www.dropbox.com/s/7igofm144vvs0c2/downloaded.zip
    Original version: https://www.dropbox.com/s/y3hhpnu6hc2yq2p/original.zip

    Here's the main HTTP loop code (I am well aware there are resource leaks in it; it's sort of a "do after fix this damn bug"):
    Code:java
    1. @Override
    2. public void run(){
    3. socket = null;
    4. running.set(true);
    5.  
    6. try{
    7. httpLog.info("Starting basic HTTP web server on port " + port + "...");
    8. socket = new ServerSocket(port);
    9. httpLog.fine("Successfully binded! Awaiting requests.");
    10. }catch (IOException e){
    11. e.printStackTrace();
    12. }
    13.  
    14. while(running.get()){
    15. try{
    16. Socket newConnection = socket.accept();
    17. BufferedReader reader = new BufferedReader(new InputStreamReader(newConnection.getInputStream()));
    18. String request = reader.readLine();
    19.  
    20. if(!request.startsWith("GET")){
    21. httpLog.warning("Recieved non-GET HTTP request. Please look into it? " + newConnection.getInetAddress().getHostAddress() + ":" + newConnection.getPort() + " tried " + request);
    22. newConnection.close();
    23. continue;
    24. }
    25.  
    26. request = request.substring(5); // At this point, it should be "somefile.ext HTTP/1.1" or whatever
    27. request = request.substring(0, request.indexOf(' ')); // Aaaaand now it should be "somefile.ext"
    28. httpLog.info(newConnection.getInetAddress().getHostAddress() + " is requesting " + request);
    29. httpLog.info("Outside URL would look like: " + this.getURLDestination(request));
    30. File target = new File(plugin.getSoundDirectory(), request);
    31. if(!target.exists()){
    32. httpLog.info("Couldn't find the requested file. Terminating connection.");
    33. newConnection.close();
    34. continue;
    35. }
    36.  
    37. FileInputStream stream = new FileInputStream(target);
    38.  
    39. WritableByteChannel out = Channels.newChannel(newConnection.getOutputStream());
    40. out.write(ByteBuffer.wrap(getOkayMessage(target).getBytes()));
    41. stream.getChannel().transferTo(0, target.length(), out);
    42. out.close();
    43.  
    44. stream.close();
    45. reader.close();
    46. }catch (IOException e){
    47. e.printStackTrace();
    48. }
    49. }
    50.  
    51. try{
    52. socket.close();
    53.  
    54. }catch (Throwable e){
    55. e.printStackTrace();
    56. }
    57. }


    and getOkayMessage():
    Code:java
    1. public String getOkayMessage(File file){
    2. return "HTTP/1.1 200 OK\r\nContent-Length: " + file.length() + "\r\nConnection: Keep-Alive\r\nServer: TestHTTP\r\nContent-Type: application/x-zip-compressed\r\n";
    3. }
     
  2. Offline

    RawCode

    dont use reader for binary files, read carefully javadocs about that class.
     
  3. Offline

    Cirno

    I'm not? I'm using it for getting the HTTP method requested.
     
  4. Offline

    garbagemule

    Speaking of reading Javadocs carefully, you should perhaps take an extra look at java.io.File.length(). Also, your choice of IO classes is a bit unorthodox. I can't help but feel like you looked like the character in your signature while writing this :p

    I'm not a huge fan of Java NIO, although NIO.2 is pretty damn sweet, if you're so lucky as to be guaranteed a deployment environment that uses Java 7. If not, and if you don't want to venture into the jungle of Java NIO, just stick with good ol' streams (and take a little time to learn what streams actually are [grab a hold of any functional language and you'll run into them very soon]). Here's a general outline of what you could do:
    1. Create a FileInputStream to read the requested file (you've already done this, so yay).
    2. Wrap the client output stream in a BufferedOutputStream.
    3. Create a byte array to serve as the buffer for reading into and copying from. Give it a sane size, maybe a couple of kilobytes (1024, 2048), but don't go to the order of megabytes - that's not what streams are about.
    4. Use the read(byte[], int, int) and write(byte[], int, int) methods of the streams in a while-loop (read the Javadocs carefully, and Google example usages if you're having trouble).
    5. Finally, close the streams.
    Happy hacking!
     
  5. Offline

    Cirno

    Didn't work :(
    and yes, I am kinda panicking like the girl in my signature. It's been annoying me for the past 2 days. I've tried your method, the standard while-read-write loop, reading everything into memory and then sending it all at once, nothing works. It keeps skipping the first part of the file; specifically, it skips all the data up until it hits the first line after the new line. The last parts confusing, so here's a picture of what I mean:
    [​IMG]

    Update: It skips the first 284 bytes of a file.
    24kb? pfft. I'm terrible at estimations. Tried doing it using a file that weighs only 82 bytes. Upon arrival, it was 0 bytes.

    Update 2: It's not 284 bytes; tried a 285 byte file and it still comes up as 0 bytes. Is this a bug within the vanilla client?
     
  6. Offline

    garbagemule

    Are you making sure to flush the BufferedOutputStream after every write? I wrote a tiny little example that works just fine with the zip-file from your Dropbox link. What do you mean by "a bug within the vanilla client"? I don't see any Minecraft-related code at all.
     
  7. Offline

    Cirno

    Code:java
    1. try(FileInputStream stream = new FileInputStream(target); BufferedOutputStream output = new BufferedOutputStream(newConnection.socket().getOutputStream());){
    2. int length;
    3. byte[] buffer = new byte[2048];
    4.  
    5. while((length = stream.read(buffer)) != -1){
    6. output.write(buffer, 0, length);
    7. output.flush();
    8. }
    9. }


    This is what I'm currently using.
    and by bug within the vanilla client, I mean that the client itself is doing this for some reason.

    edit: Client gets a "java.util.zip.ZipException: invalid END header (bad central directory offset)"
     
  8. Offline

    garbagemule

    Looks alright, apart from the call to "newConnection.socket()", which I'm sure is a typo. It could be that the client-side isn't actually ready to receive anything yet by the time you start writing (I've only tested locally). Anyway, here's a minimal working example. Fire up your browser on localhost:8080 (or change the port), and it'll send you the file "original.zip", if it is in the same folder as where you invoked the JVM.
    Code:java
    1. public class Test {
    2. private static final int PORT = 8080;
    3. private static final int BUFFER_SIZE = 2048;
    4. private static final String HEADER =
    5. "HTTP/1.1 200 OK\r\n" +
    6. "Content-Type: application/x-zip-compressed\r\n" +
    7. "Content-Length: ";
    8.  
    9. public static void main(String[] args) throws Exception {
    10. ServerSocket server = new ServerSocket(PORT);
    11.  
    12. while (true) {
    13. // Wait for connection
    14. Socket client = server.accept();
    15.  
    16. // Store file in a byte array so we can get its size
    17. FileInputStream fileIn = new FileInputStream(new File("original.zip"));
    18. write(fileIn, fileOut);
    19. fileIn.close();
    20.  
    21. // Append size of file (in bytes) to header and wrap it up
    22. String header = HEADER + fileOut.size() + "\r\n\n";
    23.  
    24. // Grab the client output stream and write the header
    25. InputStream headerIn = new ByteArrayInputStream(header.getBytes());
    26. OutputStream clientOut = new BufferedOutputStream(client.getOutputStream());
    27. write(headerIn, clientOut);
    28. headerIn.close();
    29.  
    30. // Finally, write the actual content
    31. fileOut.writeTo(clientOut);
    32. clientOut.close();
    33. fileOut.close();
    34.  
    35. // And remember to close the client connection
    36. client.close();
    37. }
    38. }
    39.  
    40. private static void write(InputStream in, OutputStream out) throws IOException {
    41. byte[] buffer = new byte[BUFFER_SIZE];
    42. int read;
    43. while ((read = in.read(buffer)) != -1) {
    44. out.write(buffer, 0, read);
    45. out.flush();
    46. }
    47. }
    48. }

    The downloaded file has the exact same size as the original:
    Code:
    $ ls -l
    total 17280
    -rw-r--r--@ 1 [snip]  staff  4415820 May  3 20:34 download.zip
    -rw-r--r--@ 1 [snip]  staff  4415820 May  3 20:34 original.zip
     
  9. Offline

    Cirno

    This works... A bit weird; I'm pretty sure I just screwed up somewhere. Thanks :p
     
Thread Status:
Not open for further replies.

Share This Page