Xolve
Xolve

Reputation: 23200

ByteBuffer - difference between encoding string and put vs CharSet encode?

I have two different ways to create ByteBuffer object from String:

  1. Get byte[] from String an use ByteBuffer.put(byte[]) method:
private ByteBuffer respWithPut() {
    ByteBuffer respBuf = ByteBuffer.allocate(1024);
    respBuf.put(httpResponse().getBytes(StandardCharsets.US_ASCII));
    return respBuf;
}
  1. Use Charset.encode(String) method:
private ByteBuffer respFromChar() {
    return StandardCharsets.US_ASCII.encode(httpResponse());
}

I am trying to send a simple HTTP response using this (complete code at the end of the question). With respWithPut() I get corrupted response on client, while respFromChar() works fine.

What am I doing wrong in respWithPut()?

Complete Sample Code:

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Arrays;
import java.util.concurrent.Future;

public class AsyncServer {
    final String HTTP_DELIM = "\r\n";

    private String httpResponse() {
        String body = "HELLO";
        String prologue = "HTTP/1.1 200 OK";
        String header = String.join(HTTP_DELIM,
                Arrays.asList(
                        "Date: " + Instant.now().toString(),
                        "Content-Type: text/plain",
                        String.format("Content-Length: %d", body.length()),
                        HTTP_DELIM
                )
        );
        return prologue + HTTP_DELIM + header +body;
    }

    private ByteBuffer respWithPut() {
        ByteBuffer respBuf = ByteBuffer.allocate(1024);
        respBuf.put(httpResponse().getBytes(StandardCharsets.US_ASCII));
        return respBuf;
    }

    private ByteBuffer respFromChar() {
        return StandardCharsets.US_ASCII.encode(httpResponse());
    }

    public void startHttpServer() throws Exception {
        AsynchronousServerSocketChannel listener
                = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080));
        while (true) {
            Future<AsynchronousSocketChannel> asyncCh = listener.accept();
            AsynchronousSocketChannel aSock = asyncCh.get();
            aSock.write(respWithPut());
            aSock.close();
        }
    }

    public static void main(String[] args) throws Exception {
        AsyncServer asyncServer = new AsyncServer();
        asyncServer.startHttpServer();
    }
}

To make a sample request, use: curl -v "http://localhost:8080/".

Upvotes: 1

Views: 342

Answers (1)

VGR
VGR

Reputation: 44328

A ByteBuffer has a position indicating where the next byte should be read from. Your respWithPut method needs to call respBuf.flip() to make sure the buffer’s position is pointing to the data you just put in it.

After ByteBuffer.allocate:

position                              limit
↓                                     ↓
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| … |_|
0 1 2 3 4 5 5 6 7 8 9 1 1 1 1 1 1     ↑
                      0 1 2 3 4 5     buffer size

After calling ByteBuffer.put with, for example, a byte array of length eight:

                  position            limit
                  ↓                   ↓
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| … |_|
0 1 2 3 4 5 5 6 7 8 9 1 1 1 1 1 1     ↑
                      0 1 2 3 4 5     buffer size

The next ByteBuffer.get call will read the byte at index 8, which is still zero since you haven’t used put to add any data there.

After calling ByteBuffer.flip, the limit will be the old position, and the new position will be zero, making any existing data ready for reading:

next get operation will read from here
↓
position          limit
↓                 ↓
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| … |_|
0 1 2 3 4 5 5 6 7 8 9 1 1 1 1 1 1     ↑
                      0 1 2 3 4 5     buffer size

Upvotes: 3

Related Questions