ByteBuffer - разница между строкой кодирования и положением и кодированием CharSet? - PullRequest
1 голос
/ 19 февраля 2020

У меня есть два разных способа создать ByteBuffer объект из String:

  1. Получить byte[] из String и использовать ByteBuffer.put(byte[]) метод:
private ByteBuffer respWithPut() {
    ByteBuffer respBuf = ByteBuffer.allocate(1024);
    respBuf.put(httpResponse().getBytes(StandardCharsets.US_ASCII));
    return respBuf;
}
Используйте Charset.encode(String) метод:
private ByteBuffer respFromChar() {
    return StandardCharsets.US_ASCII.encode(httpResponse());
}

Я пытаюсь отправить простой HTTP-ответ, используя это (полный код в конце вопроса). С respWithPut() я получаю искаженный ответ на клиенте, в то время как respFromChar() работает нормально.

Что я делаю не так в respWithPut()?

Полный пример кода:

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();
    }
}

Чтобы сделать запрос образца, используйте: curl -v "http://localhost:8080/".

1 Ответ

3 голосов
/ 21 февраля 2020

ByteBuffer имеет позицию , указывающую, откуда должен быть прочитан следующий байт. Ваш метод respWithPut должен вызвать respBuf.flip () , чтобы убедиться, что позиция буфера указывает на данные, которые вы только что в него поместили.

После 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

После вызова ByteBuffer.put, например, с байтовым массивом длины восемь:

                  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

Следующий вызов ByteBuffer.get прочитает байт по индексу 8, который по-прежнему равен нулю, так как вы не использовал put для добавления каких-либо данных.

После вызова ByteBuffer.flip предел будет старой позицией, а новая позиция будет равна нулю, что делает любые существующие данные готовыми для чтения:

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
...