1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-22 15:42:00 +03:00

Merge pull request #562 from square/jwilson_0223_readUtf8_line

Promote line reading to BufferedSource.
This commit is contained in:
Adrian Cole
2014-02-23 16:55:05 -08:00
7 changed files with 211 additions and 28 deletions

View File

@@ -145,7 +145,7 @@ public final class HttpConnection {
}
while (true) {
String statusLineString = readLine();
String statusLineString = source.readUtf8Line(true);
StatusLine statusLine = new StatusLine(statusLineString);
Response.Builder responseBuilder = new Response.Builder()
@@ -166,28 +166,11 @@ public final class HttpConnection {
/** Reads headers or trailers into {@code builder}. */
public void readHeaders(Headers.Builder builder) throws IOException {
// parse the result headers until the first blank line
for (String line; (line = readLine()).length() != 0; ) {
for (String line; (line = source.readUtf8Line(true)).length() != 0; ) {
builder.addLine(line);
}
}
private String readLine() throws IOException {
long newline = source.seek((byte) '\n');
if (newline > 0 && source.buffer().getByte(newline - 1) == '\r') {
// Read everything until '\r\n', then skip the '\r\n'.
String result = source.readUtf8((int) (newline - 1));
source.skip(2);
return result;
} else {
// Read everything until '\n', then skip the '\n'.
String result = source.readUtf8((int) (newline));
source.skip(1);
return result;
}
}
/**
* Discards the response body so that the connection can be reused and the
* cache entry can be completed. This needs to be done judiciously, since it
@@ -530,9 +513,9 @@ public final class HttpConnection {
private void readChunkSize() throws IOException {
// read the suffix of the previous chunk
if (bytesRemainingInChunk != NO_CHUNK_YET) {
readLine();
source.readUtf8Line(true);
}
String chunkSizeString = readLine();
String chunkSizeString = source.readUtf8Line(true);
int index = chunkSizeString.indexOf(";");
if (index != -1) {
chunkSizeString = chunkSizeString.substring(0, index);

View File

@@ -40,14 +40,19 @@ public interface BufferedSource extends Source {
*/
void require(long byteCount) throws IOException;
/** Removes a byte from the front of this buffer and returns it. */
byte readByte() throws IOException;
/** Removes a Big-Endian short from the front of this buffer and returns it. */
short readShort() throws IOException;
/** Removes a Little-Endian short from the front of this buffer and returns it. */
int readShortLe() throws IOException;
/** Removes a Big-Endian int from the front of this buffer and returns it. */
int readInt() throws IOException;
/** Removes a Little-Endian int from the front of this buffer and returns it. */
int readIntLe() throws IOException;
/**
@@ -57,10 +62,34 @@ public interface BufferedSource extends Source {
*/
void skip(long byteCount) throws IOException;
/** Removes {@code byteCount} bytes from this and returns them as a byte string. */
ByteString readByteString(int byteCount) throws IOException;
/**
* Removes {@code byteCount} bytes from this, decodes them as UTF-8 and
* returns the string.
*/
String readUtf8(int byteCount) throws IOException;
/**
* Removes and returns characters up to but not including the next line break.
* A line break is either {@code "\n"} or {@code "\r\n"}; these characters are
* not included in the result.
*
* <p>This method supports two ways to handle the end of the stream:
* <ul>
* <li><strong>Throw on EOF.</strong> Every call must consume either '\r\n'
* or '\n'. If these characters are absent in the stream, an {@link
* java.io.EOFException} is thrown. Use this for machine-generated data
* where a missing line break implies truncated input.
* <li><strong>Don't throw, just like BufferedReader.</strong> If the source
* doesn't end with a line break then an implicit line break is assumed.
* Null is returned once the source is exhausted. Use this for
* human-generated data, where a trailing line breaks are optional.
* </ul>
*/
String readUtf8Line(boolean throwOnEof) throws IOException;
/**
* Returns the index of {@code b} in the buffer, refilling it if necessary
* until it is found. This reads an unbounded number of bytes into the buffer.

View File

@@ -139,7 +139,6 @@ public final class OkBuffer implements BufferedSource, BufferedSink, Cloneable {
return result;
}
/** Removes a byte from the front of this buffer and returns it. */
@Override public byte readByte() {
if (byteCount == 0) throw new IllegalStateException("byteCount == 0");
@@ -171,7 +170,6 @@ public final class OkBuffer implements BufferedSource, BufferedSink, Cloneable {
}
}
/** Removes a Big-Endian short from the front of this buffer and returns it. */
@Override public short readShort() {
if (byteCount < 2) throw new IllegalArgumentException("byteCount < 2: " + byteCount);
@@ -201,7 +199,6 @@ public final class OkBuffer implements BufferedSource, BufferedSink, Cloneable {
return (short) s;
}
/** Removes a Big-Endian int from the front of this buffer and returns it. */
@Override public int readInt() {
if (byteCount < 4) throw new IllegalArgumentException("byteCount < 4: " + byteCount);
@@ -234,22 +231,18 @@ public final class OkBuffer implements BufferedSource, BufferedSink, Cloneable {
return i;
}
/** Removes a Little-Endian short from the front of this buffer and returns it. */
public int readShortLe() {
return Util.reverseBytesShort(readShort());
}
/** Removes a Little-Endian int from the front of this buffer and returns it. */
public int readIntLe() {
return Util.reverseBytesInt(readInt());
}
/** Removes {@code byteCount} bytes from this and returns them as a byte string. */
public ByteString readByteString(int byteCount) {
return new ByteString(readBytes(byteCount));
}
/** Removes {@code byteCount} bytes from this, decodes them as UTF-8 and returns the string. */
public String readUtf8(int byteCount) {
checkOffsetAndCount(this.byteCount, 0, byteCount);
if (byteCount == 0) return "";
@@ -272,6 +265,28 @@ public final class OkBuffer implements BufferedSource, BufferedSink, Cloneable {
return result;
}
@Override public String readUtf8Line(boolean throwOnEof) throws EOFException {
long newline = indexOf((byte) '\n');
if (newline == -1) {
if (throwOnEof) throw new EOFException();
return byteCount != 0 ? readUtf8((int) byteCount) : null;
}
if (newline > 0 && getByte(newline - 1) == '\r') {
// Read everything until '\r\n', then skip the '\r\n'.
String result = readUtf8((int) (newline - 1));
skip(2);
return result;
} else {
// Read everything until '\n', then skip the '\n'.
String result = readUtf8((int) (newline));
skip(1);
return result;
}
}
private byte[] readBytes(int byteCount) {
checkOffsetAndCount(this.byteCount, 0, byteCount);

View File

@@ -79,6 +79,31 @@ final class RealBufferedSource implements BufferedSource {
return buffer.readUtf8(byteCount);
}
@Override public String readUtf8Line(boolean throwOnEof) throws IOException {
long start = 0;
long newline;
while ((newline = buffer.indexOf((byte) '\n', start)) == -1) {
start = buffer.byteCount;
if (source.read(buffer, Segment.SIZE) == -1) {
if (throwOnEof) throw new EOFException();
return buffer.byteCount != 0 ? readUtf8((int) buffer.byteCount) : null;
}
}
if (newline > 0 && buffer.getByte(newline - 1) == '\r') {
// Read everything until '\r\n', then skip the '\r\n'.
String result = readUtf8((int) (newline - 1));
skip(2);
return result;
} else {
// Read everything until '\n', then skip the '\n'.
String result = readUtf8((int) (newline));
skip(1);
return result;
}
}
@Override public short readShort() throws IOException {
require(2);
return buffer.readShort();

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2014 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okio;
public final class OkBufferReadUtf8LineTest extends ReadUtf8LineTest {
@Override protected BufferedSource newSource(String s) {
return new OkBuffer().writeUtf8(s);
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2014 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okio;
import java.io.EOFException;
import java.io.IOException;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public abstract class ReadUtf8LineTest {
protected abstract BufferedSource newSource(String s);
@Test public void readLines() throws IOException {
BufferedSource source = newSource("abc\ndef\n");
assertEquals("abc", source.readUtf8Line(true));
assertEquals("def", source.readUtf8Line(true));
try {
source.readUtf8Line(true);
fail();
} catch (EOFException expected) {
}
}
@Test public void emptyLines() throws IOException {
BufferedSource source = newSource("\n\n\n");
assertEquals("", source.readUtf8Line(true));
assertEquals("", source.readUtf8Line(true));
assertEquals("", source.readUtf8Line(true));
assertTrue(source.exhausted());
}
@Test public void crDroppedPrecedingLf() throws IOException {
BufferedSource source = newSource("abc\r\ndef\r\nghi\rjkl\r\n");
assertEquals("abc", source.readUtf8Line(true));
assertEquals("def", source.readUtf8Line(true));
assertEquals("ghi\rjkl", source.readUtf8Line(true));
}
@Test public void bufferedReaderCompatible() throws IOException {
BufferedSource source = newSource("abc\ndef");
assertEquals("abc", source.readUtf8Line(false));
assertEquals("def", source.readUtf8Line(false));
assertEquals(null, source.readUtf8Line(false));
}
@Test public void bufferedReaderCompatibleWithTrailingNewline() throws IOException {
BufferedSource source = newSource("abc\ndef\n");
assertEquals("abc", source.readUtf8Line(false));
assertEquals("def", source.readUtf8Line(false));
assertEquals(null, source.readUtf8Line(false));
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2014 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okio;
import java.io.IOException;
public final class RealBufferedSourceReadUtf8LineTest extends ReadUtf8LineTest {
/** Returns a buffered source that gets bytes of {@code data} one at a time. */
@Override protected BufferedSource newSource(String s) {
final OkBuffer buffer = new OkBuffer().writeUtf8(s);
Source slowSource = new Source() {
@Override public long read(OkBuffer sink, long byteCount) throws IOException {
return buffer.read(sink, Math.min(1, byteCount));
}
@Override public Source deadline(Deadline deadline) {
throw new UnsupportedOperationException();
}
@Override public void close() throws IOException {
throw new UnsupportedOperationException();
}
};
return Okio.buffer(slowSource);
}
}