[HTTP & java] Chunked encoding faalt in browsers

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 15-09 23:08
Ik ben bezig met een kleine HTTP responder in java, maar ik heb wat probleempjes met de chunked encoding. Meerdere browsers accepteren de output niet. Enkel Firefox en Opera lijken de output van mijn app. te pakken, terwijl IE8 en chrome de reactie niet graag ontvangen. Chrome is de enige die een beetje een nuttige foutmelding geeft:

Error 321 (net::ERR_INVALID_CHUNKED_ENCODING): Unknown error.

Die zegt dus netjes dat er een fout zit in mijn chunky encoding, maar ik zou niet weten waar de fout zit.

De output van mijn app is:
code:
1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

22
Deze tekenreeks is 34 tekens lang.

0


Iedere lijn is netjes gescheiden met CRLF, zoals omschreven in de HTTP specificatie. Ook wordt de laatste 0 netjes gevolgd door een dubbele CRLF, wat niet te zien is tussen de code-tags omdat de BBCode parser de code trimt van whitespace.

Ik zou niet weten waar hier de fout in zit: waarom accepteren zowel Chrome als Internet Explorer deze output niet? Klopt de output niet of kloot java met de CRLF tekentjes?

Het platform waar ik deze output laat genereren is op dit moment een Java VM draaiende in Apple.

Om deze post nog even af te maken, het relevante deel van mijn code:
Main.java
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.io.*;
import java.net.*;

import HTTP.*;

public class Main {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(8080);
        } catch(IOException exc) {
            System.err.println(exc);
            System.exit(1);
        }

        Socket clientSocket = null;
        int count = 1;
        while(count-- > 0) {
            System.out.println((count+1)+" connections left...");
            try {
                clientSocket = serverSocket.accept();
            } catch(IOException exc) {
                System.err.println(exc);
                System.exit(1);
            }

            try {
                RequestReader request = new RequestReader(clientSocket.getInputStream());
                
                ResponseWriter response = new ResponseWriter(clientSocket.getOutputStream());
                //response.print("àáèéêëïóöü€×÷");
                response.print("Deze tekenreeks is 34 tekens lang.");
                response.finish();
            } catch(Exception e) {
               System.err.println(e);
            }
            
            clientSocket.close();
        }

        serverSocket.close();
    }

}


HTTP/ResponseWriter.java
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package HTTP;

import HTTP.Exceptions.HttpHeadersAlreadySentException;

import java.io.OutputStream;
import java.io.PrintWriter;

/**
 * A Response-extension which enables the Response to write itself on a outputStream.
 * @author tobyhinloopen
 */
public class ResponseWriter extends Response {
    protected final PrintWriter outputStream;
    private boolean headersSend = false;

    /**
     * Creates a new ResponseWriter
     * @param outputStream
     */
    public ResponseWriter(OutputStream outputStream) {
        this.outputStream = new PrintWriter(outputStream);
    }

    /**
     * Try to send the currently generated output.
     */
    public void flush() {
        trySendHeaders();
        try {
            outputStream.print(Integer.toHexString(output.length())+"\r\n");
            outputStream.print(output+"\r\n\r\n");
            output = "";
            outputStream.flush();
        } catch(Exception e) {
            System.err.println(e);
        }
    }

    /**
     * Try to send the headers.
     * @return TRUE if the headers are sent, FALSE if the headers are already sent.
     */
    public boolean trySendHeaders() {
        if(headersSend) {
            return false;
        }
        try {
            sendHeaders();
            return true;
        } catch(HttpHeadersAlreadySentException e) {
            return false;
        }
    }

    /**
     * Send all headers.
     * @throws HttpHeadersAlreadySentException
     */
    public void sendHeaders() throws HttpHeadersAlreadySentException {
        if(headersSend) {
            throw new HttpHeadersAlreadySentException();
        }
        outputStream.print("HTTP/1.1 200 OK\r\n");
        outputStream.print("Content-Type: "+contentType+"\r\n");
        outputStream.print("Transfer-Encoding: chunked\r\n\r\n");
        headersSend = true;
    }

    /**
     * Sends the complete response and closes the outputStream.
     */
    public void finish() {
        flush();
        outputStream.print("0\r\n\r\n");
        outputStream.flush();
        outputStream.close();
    }
    
}


HTTP/Response.java
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package HTTP;

/**
 * A Class representing a HTTP Response.
 * @author tobyhinloopen
 */
public class Response {
    protected String contentType = "text/plain";
    protected String output = "";

    /**
     * Appends <code>String</code> to the output.
     * @param string The string to append.
     */
    public void print(String string) {
        output += string;
    }
}

[ Voor 0% gewijzigd door Gamebuster op 13-01-2010 21:29 . Reden: Typo ]

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 05:18
Je maakt een fout in het formatteren van de chunks. Elke chunk (zonder extra headers) heeft het formaat "<grootte>\r\n<data>\r\n", waarbij grootte de lengte van data in bytes in hexadecimale notatie is. In je code (en in je voorbeelduitvoer) plaats je echter twee extra bytes ("\r\n") achter data, wat verkeerd is.

Waarschijnlijk gaat het daardoor fout, want na de eerste chunk gelezen te hebben, verwacht de webserver een volgende chunkgrootte te lezen (of een 0) maar hij ziet een lege regel.

Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 15-09 23:08
Ah, bedankt. :)

Volgende keer toch maar wat minder wikipedia lezen dus :P

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • user109731
  • Registratie: Maart 2004
  • Niet online
Gamebuster schreef op woensdag 13 januari 2010 @ 22:55:
Ah, bedankt. :)

Volgende keer toch maar wat minder wikipedia lezen dus :P
Het voorbeeld daar is erg misleidend maar klopt wel volgens mij. De string "This is the data in the first chunk" is namelijk 35 (= 0x23 hex) tekens lang, terwijl ze 25 meegeven. De witregel erna is dus onderdeel van de string en geen separator... Eigenlijk zou het aangepast moeten worden want het is wel erg onduidelijk zo :)

[ Voor 4% gewijzigd door user109731 op 13-01-2010 23:02 ]


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 05:18
Gamebuster schreef op woensdag 13 januari 2010 @ 22:55:
Volgende keer toch maar wat minder wikipedia lezen dus :P
Of volgende keer beter Wikipedia lezen. :P Als je de lengte van de tekst op Wikipedia vergelijkt met de chunk lengte die gegeven wordt, dan zie je dat je twee letters tekort komt, als je de newline karakters niet meeneemt als onderdeel van de data. ;)
JanDM schreef op woensdag 13 januari 2010 @ 23:00:
Eigenlijk zou het aangepast moeten worden want het is wel erg onduidelijk zo :)
Het is Wikipedia he, dus wat let je om 't te verbeteren? ;-)

Acties:
  • 0 Henk 'm!

  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
Er gaat nog wel meer fout in je code:
Je doet niet aan encoding, dus technisch gezien is alleen US-ASCII ofwel karakters 0-127 toegestaan.
Je baseert de lengte van een chunk op de lengte van de string en niet op het aantal bytes. Dat gaat dus alleen maar goed zolang je karakters in ASCII en je eigen system encoding gebruikt.

Ik neem aan dat je dit schrijft als een technische uitdaging, en niet als iets dat in productie genomen gaat worden, want anders zou ik je ten zeerste aanraden een bestaande HTTP server (en/of servlet container) te gebruiken (bijvoorbeeld Jetty).

[ Voor 11% gewijzigd door Remus op 14-01-2010 09:25 ]


Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 15-09 23:08
Remus schreef op donderdag 14 januari 2010 @ 09:23:
Er gaat nog wel meer fout in je code:
Je doet niet aan encoding, dus technisch gezien is alleen US-ASCII ofwel karakters 0-127 toegestaan.
Je baseert de lengte van een chunk op de lengte van de string en niet op het aantal bytes. Dat gaat dus alleen maar goed zolang je karakters in ASCII en je eigen system encoding gebruikt.

Ik neem aan dat je dit schrijft als een technische uitdaging, en niet als iets dat in productie genomen gaat worden, want anders zou ik je ten zeerste aanraden een bestaande HTTP server (en/of servlet container) te gebruiken (bijvoorbeeld Jetty).
Ik was me al bewust van het encoding-probleempje. Is tevens al opgelost, aangezien hij nu netjes een euro-teken eruit spuugt in UTF-8 encoding, welke netjes als 3 bytes telt.

Ik zal ook eens kijken naar Jetty, bedankt voor de info.

[ Voor 3% gewijzigd door Gamebuster op 14-01-2010 14:43 ]

Let op: Mijn post bevat meningen, aannames of onwaarheden

Pagina: 1