How to download an image with a Java socket HTTP/1

2020-04-21 02:03发布

问题:

I am trying to download images using java.net.Socket without java.net.URL and external libraries. Here is what I have and I am not sure what isn't working.

        String domain = "www.manchester.edu";
        String path = "/images/default-source/default-album/slide1.jpg";
        Socket socket = new Socket(domain,80);

        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
        out.println("" +
                "Get "+path+" HTTP/1.1\n" +
                "Host: "+domain+"\n"+
                "");
        out.println();
        out.flush();

        BufferedImage image = ImageIO.read(socket.getInputStream());

In order to see what is coming through the stream, exchange the BufferedImage line for:

    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    String inputLine;
    while ((inputLine = in.readLine()) != null && inputLine.trim() != "0") {
       System.out.println(inputLine);
    }

Presumably the ImageIO.read(...) method does not expect the HTTP header in the socket input stream. But I am not sure how to remove the header. I've tried reading the header lines with BufferedReader and then passing the socket input stream to ImageIO.read(...) but that did not work.

Here is the string printed by BufferedReader:

HTTP/1.1 200 OK
Cache-Control: public, max-age=7776000
Content-Length: 96876
Content-Type: image/jpeg
Expires: Thu, 04 Feb 2016 21:36:46 GMT
Last-Modified: Tue, 15 Sep 2015 14:23:40 GMT
Server: Microsoft-IIS/8.5
content-disposition: inline; filename=slide1.jpg
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 06 Nov 2015 21:36:46 GMT

����...

The non-printable characters at the end seems to indicate that what follows the header is an image of some sort. But how can I turn this into a java.awt.image.BufferedImage or a javafx.scene.image.Image? The latter has a constructor that takes an input stream, and I've tried that, but it doesn't work (because of the http header?). This question is similar to this one, but I am trying to create an image not a file.

回答1:

Using BufferedReader is a mistake for 2 reasons:

  1. It converts bytes to a String which you then convert back into bytes to send it into the output stream. The conversions may (and probably will) lead to loss of data;
  2. It parses too many bytes and you have no control over it.

You need to approach this surgically, creating a buffer of bytes of the size you want and using an InputStream to read the stream byte-by-byte on your own terms. Also, since you know that the HTTP header ending is "\r\n\r\n" (or 13 10 13 10 in bytes), you can scan your own buffer for this pattern and act accordingly.

Your best bet is to download the image to a file and then use the ImageIO to read it from the local file.

Here's the code that will allow you to download an Image file (or any other file) by cutting out the header:

    // Initialize the streams.
    final FileOutputStream fileOutputStream = new FileOutputStream(file);
    final InputStream inputStream = socket.getInputStream();

    // Header end flag.
    boolean headerEnded = false;

    byte[] bytes = new byte[2048];
    int length;
    while ((length = inputStream.read(bytes)) != -1) {
        // If the end of the header had already been reached, write the bytes to the file as normal.
        if (headerEnded)
            fileOutputStream.write(bytes, 0, length);

        // This locates the end of the header by comparing the current byte as well as the next 3 bytes
        // with the HTTP header end "\r\n\r\n" (which in integer representation would be 13 10 13 10).
        // If the end of the header is reached, the flag is set to true and the remaining data in the
        // currently buffered byte array is written into the file.
        else {
            for (int i = 0; i < 2045; i++) {
                if (bytes[i] == 13 && bytes[i + 1] == 10 && bytes[i + 2] == 13 && bytes[i + 3] == 10) {
                    headerEnded = true;
                    fileOutputStream.write(bytes, i+4 , 2048-i-4);
                    break;
                }
            }
        }
    }
    inputStream.close();
    fileOutputStream.close();