0

I'm trying to write a simple HTTP Client & Server using the new (JDK 11) HttpClient.

JDK.: OpenJDK 17.0.0

The Client is connecting to the Server but doesn't seem to be POST'ing any data.

After the Server receives the incoming Connection, it tries to read the data.
The read never ends.
Does anyone know what I'm doing wrong?

Server Source:

package de.ipp.client.server;

import java.io.*;
import java.net.*;
import java.time.ZonedDateTime;

public class IppServer {

    public static final int PORT = 31613;

    public static void main(String[] args) throws IOException {

        System    .out.println(ZonedDateTime.now() + " IppServer Start.....");

        try(final ServerSocket serverSocket = new ServerSocket(PORT))
        {
            System.out.println(ZonedDateTime.now() + " IppServer Running..: " + serverSocket);

            while (true) {
                final Thread thread = new Thread(getSocketRunnable(serverSocket.accept()));
                ;            thread.setDaemon(true);
                ;            thread.start();
            }
        }
    }

    private static Runnable getSocketRunnable(final Socket socket) {
        return () -> {
            System    .out.println(ZonedDateTime.now() + " IppServer Socket...: incoming -> " + socket);

            try(final InputStream ist   = socket.getInputStream())
            {
                System.out.println(ZonedDateTime.now() + " IppServer Reading...");

                final byte[]      bytes = ist.readAllBytes();

                System.out.println(ZonedDateTime.now() + " IppServer L'Bytes..: " + bytes.length);
            }
            catch (final IOException e) {
                System.out.println(ZonedDateTime.now() + " IppServer Error!!..: " + e.getMessage());
            }
        };
    }
}

Client Source:

package de.ipp.client.server;

import java.net.URI;
import java.net.http.*;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest.*;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.ZonedDateTime;

public class IppClient {

    public static void main(final String[] args) throws Exception {

        final URI                  uri       = new URI("http://localhost:" + IppServer.PORT + "/printers");
        final BodyPublisher        publisher = HttpRequest.BodyPublishers.ofByteArray("SomeBytes".getBytes());

        final Builder              builder   = HttpRequest.newBuilder();
        ;                          builder.uri      (uri);
        ;                          builder.version  (Version.HTTP_1_1);
        ;                          builder.setHeader("Transfer-Encoding", "chunked");
        ;                          builder.setHeader("Content-Type",      "application/ipp");
        ;                          builder.setHeader("Accept-Encoding",   "gzip,deflate");
        ;                          builder.POST(publisher);

        final HttpRequest          request   = builder.build();

        System.out.println(ZonedDateTime.now() + " IppClient Posting..: " + uri);

        final HttpResponse<byte[]> response  = HttpClient.newHttpClient().send(request, BodyHandlers.ofByteArray());

        System.out.println(ZonedDateTime.now() + " IppClient Response.: " + response.statusCode());
    }
}

Logs:

2022-06-02T11:52:23.378674200+02:00[Europe/Berlin] IppServer Start.....
2022-06-02T11:52:23.411587200+02:00[Europe/Berlin] IppServer Running..: ServerSocket[addr=0.0.0.0/0.0.0.0,localport=31613]
2022-06-02T11:53:31.229082300+02:00[Europe/Berlin] IppServer Socket...: incoming -> Socket[addr=/127.0.0.1,port=55978,localport=31613]
2022-06-02T11:53:31.232074700+02:00[Europe/Berlin] IppServer Reading...

2022-06-02T11:53:30.474583800+02:00[Europe/Berlin] IppClient Posting..: http://localhost:31613/printers
Dave The Dane
  • 650
  • 1
  • 7
  • 18
  • What happens if you remove `builder.setHeader("Transfer-Encoding", "chunked");`? Because that sounds like an option that should be decided by the http client itself, not by you. Same goes probably for `builder.setHeader("Accept-Encoding", "gzip,deflate");`. – Mark Rotteveel Jun 02 '22 at 10:34
  • Thanks for the suggestion. Unfortunately it made no difference. – Dave The Dane Jun 02 '22 at 10:49
  • **Your server is not an HTTP server.** It tries to `readAllBytes` on the socket, which reads until the peer (client) disconnects, but nonancient HTTP (1.0' up) doesn't disconnect and doesn't delimit requests or responses using disconnection. See RFC7230 et seq, or 2616, or wikipedia. Either use something that actually is an HTTP server like com.sun.net.httpserver, Glassfish, Tomcat, Jetty, Netty; or _correctly_ implement HTTP, which is a good deal of work, which is exactly why products like the above exist. – dave_thompson_085 Jun 02 '22 at 13:10
  • @dave_thompson_085 Thanks. readAllBytes was the vital clue! I've changed it to just do reads & the data are getting through. I'm writing a minimalistic CUPS Client to replace CUPS4J & the Server was just to see whats going over the line. I can now see my Client is sending the right content, albeit split over 2 packets & with a Content-Length header, which the apache Client in CUPS4J did not have. It's still not working, but I think you've helped me get a lot closer. I'm now getting an rc=200, so I think I just have to get it to send it all as 1 packet or omit Content-Length or both. – Dave The Dane Jun 02 '22 at 15:52
  • ...Further to that previous comment: having fixed the readAllBytes issue, I was able to get my content correct & send it to the real CUPS Server, which yielded rc=200 ("ok") but no data in the Log in the Eclipse Console. But actually 88,000 bytes were being returned!! It turned out, the logging of the 88,000 bytes returned was just not being displayed in the Console for some reason (an issue with Eclipse, or maybe SLF4J?). Anyway, having split the output into bite-sized chunks of 4096, the logging displayed the contents just fine. – Dave The Dane Jun 03 '22 at 05:01

1 Answers1

0

Once again, many thanks to @dave_thompson_085 for the comments regarding readAllBytes.

That was a great help.

It was not the intention to write an HTTP-Server, just find exactly what CUPS4J was sending over the line & write a Client based on the new (JDK 11) HttpClient to replace it.

Here are my modified sources & some logging output...

Client Source:

package de.ipp.client.server;

import java.net.URI;
import java.net.http.*;
import java.net.http.HttpRequest.*;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Duration;

public class IppClient {

    public static final byte[] PAYLOAD = "SomeBytes".getBytes();

    public static void main(final String[] args) throws Exception {
        Thread.currentThread().setName("myClient");

        final URI         uri     = new URI("http://localhost:" + IppServer.PORT + "/printers");

        final Builder     builder = HttpRequest.newBuilder();
        ;                 builder.uri      (uri);
        ;                 builder.setHeader("Content-Type", "application/ipp");
        ;                 builder.POST     (HttpRequest.BodyPublishers.ofByteArray(PAYLOAD));

        System.out.println(IppServer.logPrefix() + "IppClient Posting.: " + uri);

        HttpClient.newBuilder().build().sendAsync(builder.build(), BodyHandlers.ofByteArray());

        Thread.sleep(Duration.ofSeconds(3).toMillis()); // (give Server time to serve!!)

        System.out.println(IppServer.logPrefix() + "IppClient Posted... (async)");
    }
}

Server Source:

package de.ipp.client.server;

import java.io.*;
import java.net.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;

public class IppServer {

    private static final DateTimeFormatter HH_MM_SS_NANOS = DateTimeFormatter.ofPattern("HH.mm.ss.SSSSSSSSS' '");

    public  static final int PORT = 31613;

    public  static void main(final String[] args) throws IOException {
        Thread.currentThread().setName("LISTENER");

        System        .out.println(logPrefix() + "IppServer Start....");

        try(final ServerSocket serverSocket = new ServerSocket(PORT))
        {
            System    .out.println(logPrefix() + "IppServer Running.: " + serverSocket);

            while (true) {
                final Socket socket = serverSocket.accept();

                System.out.println(logPrefix() + "IppServer Socket..: incoming -> " + socket);

                final Thread thread = new Thread(getSocketRunnable(socket));
                ;            thread.setDaemon(true);
                ;            thread.start();
            }
        }
    }

    private static Runnable getSocketRunnable(final Socket socket) {
        return () -> {
            try(final InputStream ist   = socket.getInputStream())
            {
                System.out.println(logPrefix() + "IppServer Reading..");

                final byte[]      buf   = new byte[4096];
                ;     int         count;

                while ((count = ist.read(buf)) != -1) {
                    logBytes(buf, count);

                    if (0 == Arrays.compare(buf, 0, count, IppClient.PAYLOAD, 0, IppClient.PAYLOAD.length)) {
                        /*
                         * For our purposes, it is sufficient that the payload was received OK.
                         * We do not need to complete the HTTP Handshake, so just break off the dialogue...
                         */
                        break;
                    }
                }
            }
            catch (final IOException e) {
                System.out.println(logPrefix() + "IppServer Error!!.: " + e.getMessage());
            }
        };
    }

    private static void logBytes(final byte[] bytes, final int count) {

        final StringBuilder sbd = new StringBuilder(count * 2);
        ;     byte          b;

        for (int i=0; i < count; i++) {
            b = bytes[i];

            if (b > 32   ) {sbd.append( (char) b);  continue;}

            if (b == '\r') {sbd.append("[\\r]");    continue;}
            if (b == '\n') {sbd.append("[\\n]");    continue;}

            sbd.append("[0x").append(Integer.toHexString(b)).append(']');
        }
        System.out.println(logPrefix() + "IppServer Bytes...: L=" + count + '\t' + sbd.toString());
    }

    public static String logPrefix() {
        return '[' + Thread.currentThread().getName() + "] " + HH_MM_SS_NANOS.format(LocalDateTime.now());
    }
}

Logs:

[LISTENER] 08.49.25.232446900 IppServer Start....
[LISTENER] 08.49.25.261367100 IppServer Running.: ServerSocket[addr=0.0.0.0/0.0.0.0,localport=31613]
[LISTENER] 08.49.38.749640100 IppServer Socket..: incoming -> Socket[addr=/127.0.0.1,port=65073,localport=31613]
[Thread-0] 08.49.38.753629400 IppServer Reading..
[Thread-0] 08.49.38.776568000 IppServer Bytes...: L=246 POST[0x20]/printers[0x20]HTTP/1.1[\r][\n]Connection:[0x20]Upgrade,[0x20]HTTP2-Settings[\r][\n]Content-Length:[0x20]9[\r][\n]Host:[0x20]localhost:31613[\r][\n]HTTP2-Settings:[0x20]AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA[\r][\n]Upgrade:[0x20]h2c[\r][\n]User-Agent:[0x20]Java-http-client/17.0.1[\r][\n]Content-Type:[0x20]application/ipp[\r][\n][\r][\n]
[Thread-0] 08.49.38.780556300 IppServer Bytes...: L=9   SomeBytes
[LISTENER] 08.50.22.853368800 IppServer Socket..: incoming -> Socket[addr=/127.0.0.1,port=65091,localport=31613]
[Thread-1] 08.50.22.854365100 IppServer Reading..
[Thread-1] 08.50.22.880294200 IppServer Bytes...: L=246 POST[0x20]/printers[0x20]HTTP/1.1[\r][\n]Connection:[0x20]Upgrade,[0x20]HTTP2-Settings[\r][\n]Content-Length:[0x20]9[\r][\n]Host:[0x20]localhost:31613[\r][\n]HTTP2-Settings:[0x20]AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA[\r][\n]Upgrade:[0x20]h2c[\r][\n]User-Agent:[0x20]Java-http-client/17.0.1[\r][\n]Content-Type:[0x20]application/ipp[\r][\n][\r][\n]
[Thread-1] 08.50.22.885280900 IppServer Bytes...: L=9   SomeBytes
[LISTENER] 09.01.07.880173600 IppServer Socket..: incoming -> Socket[addr=/127.0.0.1,port=65397,localport=31613]
[Thread-2] 09.01.07.881169400 IppServer Reading..
[Thread-2] 09.01.07.908097800 IppServer Bytes...: L=246 POST[0x20]/printers[0x20]HTTP/1.1[\r][\n]Connection:[0x20]Upgrade,[0x20]HTTP2-Settings[\r][\n]Content-Length:[0x20]9[\r][\n]Host:[0x20]localhost:31613[\r][\n]HTTP2-Settings:[0x20]AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA[\r][\n]Upgrade:[0x20]h2c[\r][\n]User-Agent:[0x20]Java-http-client/17.0.1[\r][\n]Content-Type:[0x20]application/ipp[\r][\n][\r][\n]
[Thread-2] 09.01.07.914083500 IppServer Bytes...: L=9   SomeBytes

[myClient] 09.01.06.956313800 IppClient Posting.: http://localhost:31613/printers
[myClient] 09.01.10.783634700 IppClient Posted... (async)
Dave The Dane
  • 650
  • 1
  • 7
  • 18