diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/Buffer.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/Buffer.java new file mode 100644 index 00000000000..f63ee6722f2 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/Buffer.java @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * Compatibility layer for OkHttp. + */ +public class Buffer { + + private final ByteArrayOutputStream byteStream; + + public Buffer() { + this(new byte[0]); + } + + public Buffer(byte[] bytes) { + byteStream = new ByteArrayOutputStream(); + if (bytes != null) { + byteStream.write(bytes, 0, bytes.length); + } + } + + public final byte[] getBytes() { + return byteStream.toByteArray(); + } + + public final long size() { + return byteStream.size(); + } + + public final String readUtf8() { + return new String(getBytes(), StandardCharsets.UTF_8); + } + + public final Buffer write(byte[] bytes) { + return write(bytes, 0, bytes.length); + } + + public Buffer write(byte[] bytes, int off, int len) { + byteStream.write(bytes, off, len); + return this; + } + + public final Buffer writeString(String string, Charset charset) { + return write(string.getBytes(charset)); + } + + public final Buffer writeUtf8(String string) { + return writeString(string, StandardCharsets.UTF_8); + } + + public final void flush() { + // NO-OP + } + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/ByteString.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/ByteString.java new file mode 100644 index 00000000000..68646a98017 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/ByteString.java @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + * Compatibility layer for OkHttp. + */ +public class ByteString { + + private final byte[] data; + + private ByteString(byte[] data) { + this.data = data; + } + + public static ByteString of(byte... data) { + Objects.requireNonNull(data, "data === null"); + return new ByteString(data.clone()); + } + + public static ByteString encodeUtf8(String s) { + Objects.requireNonNull(s, "s === null"); + return new ByteString(s.getBytes(StandardCharsets.UTF_8)); + } + + public final String utf8() { + return new String(data, StandardCharsets.UTF_8); + } + + public final byte[] toByteArray() { + return data.clone(); + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/Dispatcher.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/Dispatcher.java new file mode 100644 index 00000000000..14e4b6c28de --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/Dispatcher.java @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +public abstract class Dispatcher { + + public abstract MockResponse dispatch(RecordedRequest request); + + public void shutdown() { + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/Headers.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/Headers.java new file mode 100644 index 00000000000..32f00a585d3 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/Headers.java @@ -0,0 +1,142 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Headers are case-insensitive for HTTP/1.1 and must be lower-cased for HTTP/2. + */ +public class Headers { + + private final Map> headerMap; + + Headers(Map> headerMap) { + this.headerMap = headerMap; + } + + public final List headers(String key) { + return Collections.unmodifiableList(headerMap.getOrDefault(keySanitize(key), Collections.emptyList())); + } + + public final String header(String key) { + final List values = headers(key); + if (values.size() == 1) { + return values.get(0); + } + if (values.isEmpty()) { + return null; + } + return String.join(",", values); + } + + public final String get(String name) { + return header(name); + } + + /** + * Compatibility layer for OkHttp + * + * @return a Map of the headers with lower-cased keys + */ + public final Map> toMultimap() { + return headerMap.entrySet().stream() + .collect(Collectors.toMap(e -> keySanitize(e.getKey()), Map.Entry::getValue, (old, neu) -> neu)); + } + + private static String keySanitize(String header) { + return header.trim().toLowerCase(Locale.ROOT); + } + + public final Builder newBuilder() { + return new Builder(new LinkedHashMap<>(headerMap)); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private final Headers mockHttpHeaders; + + public Builder() { + this(new LinkedHashMap<>()); + } + + private Builder(Map> headers) { + this.mockHttpHeaders = new Headers(headers); + } + + public Headers build() { + return mockHttpHeaders; + } + + public Builder add(String key, String value) { + mockHttpHeaders.headerMap.compute(keySanitize(key), (k, values) -> { + if (values == null) { + values = new ArrayList<>(); + } + values.add(value); + return values; + }); + return this; + } + + public Builder add(String header) { + final int index = header.indexOf(":"); + if (index == -1) { + throw new IllegalArgumentException("Unexpected header: " + header); + } + return add(header.substring(0, index), header.substring(index + 1)); + } + + public Builder addAll(Iterable> headers) { + for (Map.Entry header : headers) { + add(header.getKey(), header.getValue()); + } + return this; + } + + public Builder removeAll(String key) { + mockHttpHeaders.headerMap.remove(key); + return this; + } + + public Builder set(String name, String value) { + return setHeader(name, value); + } + + public Builder setHeader(String key, String value) { + final List values = new ArrayList<>(); + values.add(value); + mockHttpHeaders.headerMap.put(keySanitize(key), values); + return this; + } + + public Builder setHeaders(Headers headers) { + this.mockHttpHeaders.headerMap.clear(); + this.mockHttpHeaders.headerMap.putAll(headers.toMultimap()); + return this; + } + + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/HttpUrl.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/HttpUrl.java new file mode 100644 index 00000000000..6950d735c4a --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/HttpUrl.java @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +import io.netty.handler.codec.http.QueryStringDecoder; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; + +/** + * Compatibility layer for OkHttp. + */ +public class HttpUrl { + + private final URI uri; + + public HttpUrl(URI uri) { + this.uri = uri; + } + + public final URI uri() { + return uri; + } + + public final String encodedPath() { + return uri().getRawPath(); + } + + public final String queryParameter(String name) { + final List values = new QueryStringDecoder(uri()).parameters().get(name); + if (values == null || values.isEmpty()) { + return null; + } + return values.iterator().next(); + } + + @Override + public final String toString() { + return uri().toString(); + } + + public static HttpUrl fromUrl(URL url) { + try { + return new HttpUrl(url.toURI()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Invalid URL: " + url, e); + } + } + + public static HttpUrl parse(String url) { + try { + return fromUrl(new URL(url)); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Invalid URL: " + url, e); + } + } + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/MediaType.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/MediaType.java new file mode 100644 index 00000000000..16f0ddb1cc1 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/MediaType.java @@ -0,0 +1,121 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Compatibility layer for OkHttp. + */ +public class MediaType { + private static final String TOKEN = "([a-zA-Z0-9-!#$%&'*+.^_`{|}~]+)"; + private static final String QUOTED = "\"([^\"]*)\""; + private static final Pattern TYPE_SUBTYPE = Pattern.compile(TOKEN + "/" + TOKEN); + private static final Pattern PARAMETER = Pattern.compile( + ";\\s*(?:" + TOKEN + "=(?:" + TOKEN + "|" + QUOTED + "))?"); + + private final String mediaType; + private final String type; + private final String subtype; + private final String charset; + + public MediaType(String mediaType, String type, String subtype, String charset) { + this.mediaType = mediaType; + this.type = type; + this.subtype = subtype; + this.charset = charset; + } + + public static MediaType get(String mediaType) { + Matcher typeSubtype = TYPE_SUBTYPE.matcher(mediaType); + if (!typeSubtype.lookingAt()) { + throw new IllegalArgumentException("No subtype found for: \"" + mediaType + '"'); + } + final String type = typeSubtype.group(1).toLowerCase(Locale.US); + final String subtype = typeSubtype.group(2).toLowerCase(Locale.US); + + String charset = null; + Matcher parameter = PARAMETER.matcher(mediaType); + for (int s = typeSubtype.end(); s < mediaType.length(); s = parameter.end()) { + parameter.region(s, mediaType.length()); + if (!parameter.lookingAt()) { + throw new IllegalArgumentException("Parameter is not formatted correctly: \"" + + mediaType.substring(s) + + "\" for: \"" + + mediaType + + '"'); + } + + final String name = parameter.group(1); + if (name == null || !name.equalsIgnoreCase("charset")) { + continue; + } + final String currentCharset = extractCharset(parameter.group(2), parameter.group(3)); + if (charset != null && !currentCharset.equalsIgnoreCase(charset)) { + throw new IllegalArgumentException("Multiple charsets defined: \"" + + charset + + "\" and: \"" + + currentCharset + + "\" for: \"" + + mediaType + + '"'); + } + charset = currentCharset; + } + return new MediaType(mediaType, type, subtype, charset); + } + + private static String extractCharset(String token, String quoted) { + final String charsetParameter; + if (token != null) { + // If the token is 'single-quoted' it's invalid! But we're lenient and strip the quotes. + charsetParameter = (token.startsWith("'") && token.endsWith("'") && token.length() > 2) + ? token.substring(1, token.length() - 1) + : token; + } else { + // Value is "double-quoted". That's valid and our regex group already strips the quotes. + charsetParameter = quoted; + } + return charsetParameter; + } + + public static MediaType parse(String string) { + try { + return get(string); + } catch (Exception ex) { + return null; + } + } + + public final String type() { + return type; + } + + public final String subtype() { + return subtype; + } + + public final String charset() { + return charset; + } + + @Override + public final String toString() { + return mediaType; + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/MockResponse.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/MockResponse.java new file mode 100644 index 00000000000..690d0ee0d5b --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/MockResponse.java @@ -0,0 +1,178 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +import java.time.Duration; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class MockResponse implements Response { + + private Headers.Builder headers; + private int code; + private Buffer body; + private Duration bodyDelay; + private WebSocketListener webSocketListener; + + public MockResponse() { + this.headers = Headers.builder(); + } + + @Override + public String getStatus() { + // TODO: See if we can avoid hard-coding stuff unlike OkHttp + final String reason; + if (code >= 100 && code < 200) { + reason = "Informational"; + } else if (code >= 200 && code < 300) { + reason = "OK"; + } else if (code >= 300 && code < 400) { + reason = "Redirection"; + } else if (code >= 400 && code < 500) { + reason = "Client Error"; + } else if (code >= 500 && code < 600) { + reason = "Server Error"; + } else { + reason = "Mock Response"; + } + return "HTTP/1.1 " + code + " " + reason; + } + + @Override + public int code() { + return code; + } + + @Override + public Headers getHeaders() { + return headers.build(); + } + + @Override + public Buffer getBody() { + return body; + } + + @Override + public WebSocketListener getWebSocketListener() { + return webSocketListener; + } + + public List headers(String key) { + return headers.build().headers(key); + } + + public String header(String key) { + return headers.build().header(key); + } + + public MockResponse setResponseCode(int code) { + this.code = code; + return this; + } + + public MockResponse setBody(Buffer body) { + this.body = body; + return this; + } + + public MockResponse setBody(byte[] body) { + return setBody(new Buffer(body)); + } + + public MockResponse setBody(String body) { + return setBody(new Buffer().writeUtf8(body)); + } + + public MockResponse setChunkedBody(Buffer body, int maxChunkSize) { + removeHeader("Content-Length"); + setHeader("Transfer-encoding", "chunked"); + + final Buffer chunkedBody = new Buffer(); + final byte[] bodyBytes = body.getBytes(); + int offset = 0; + while (offset < bodyBytes.length) { + final int chunkSize = Math.min(bodyBytes.length - offset, maxChunkSize); + chunkedBody.writeUtf8(Integer.toHexString(chunkSize)); + chunkedBody.writeUtf8("\r\n"); + chunkedBody.write(bodyBytes, offset, chunkSize); + chunkedBody.writeUtf8("\r\n"); + offset += chunkSize; + } + chunkedBody.writeUtf8("0\r\n"); // Last chunk. Trailers follow! + + this.body = chunkedBody; + return this; + } + + public MockResponse setChunkedBody(String body, int maxChunkSize) { + return setChunkedBody(new Buffer().writeUtf8(body), maxChunkSize); + } + + public Duration getBodyDelay() { + return bodyDelay; + } + + public MockResponse setBodyDelay(long delay, TimeUnit delayUnit) { + return setBodyDelay(Duration.ofMillis(delayUnit.toMillis(delay))); + } + + public MockResponse setBodyDelay(Duration bodyDelay) { + this.bodyDelay = bodyDelay; + return this; + } + + public MockResponse clearHeaders() { + headers = Headers.builder(); + return this; + } + + public MockResponse addHeader(String header) { + headers.add(header); + return this; + } + + public MockResponse addHeader(String name, Object value) { + headers.add(name, String.valueOf(value)); + return this; + } + + public MockResponse setHeaders(Headers headers) { + this.headers = headers.newBuilder(); + return this; + } + + public MockResponse setHeader(String name, Object value) { + headers.setHeader(name, String.valueOf(value)); + return this; + } + + public MockResponse removeHeader(String name) { + headers.removeAll(name); + return this; + } + + public MockResponse withWebSocketUpgrade(WebSocketListener listener) { + // TODO: Check if this is necessary with Vert.x + // setStatus("HTTP/1.1 101 Switching Protocols"); + // setHeader("Connection", "Upgrade"); + // setHeader("Upgrade", "websocket"); + body = null; + webSocketListener = listener; + return this; + } + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/QueueDispatcher.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/QueueDispatcher.java new file mode 100644 index 00000000000..19785ddfc10 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/QueueDispatcher.java @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Compatibility layer for OkHttp. + */ +public class QueueDispatcher extends Dispatcher { + + private static final MockResponse DEAD_LETTER = new MockResponse().setResponseCode(503); + private final BlockingQueue responseQueue; + + public QueueDispatcher() { + this.responseQueue = new LinkedBlockingQueue<>(); + } + + @Override + public MockResponse dispatch(RecordedRequest request) { + final MockResponse ret; + try { + ret = responseQueue.take(); + if (ret == DEAD_LETTER) { + // Re-add the dead letter to the queue (server is shutting down) + responseQueue.add(DEAD_LETTER); + } + return ret; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Interrupted while waiting for response", e); + } + } + + @Override + public void shutdown() { + responseQueue.add(DEAD_LETTER); + } + + public final void enqueueResponse(MockResponse response) { + responseQueue.add(response); + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/RecordedHttpConnection.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/RecordedHttpConnection.java new file mode 100644 index 00000000000..9a066ae41b5 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/RecordedHttpConnection.java @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +import io.vertx.core.net.SocketAddress; + +import java.util.Objects; +import java.util.UUID; + +public class RecordedHttpConnection { + + private final UUID id; + private final SocketAddress remoteAddress; + private final SocketAddress localAddress; + private final boolean ssl; + + public RecordedHttpConnection(SocketAddress remoteAddress, SocketAddress localAddress, boolean ssl) { + this.remoteAddress = remoteAddress; + this.localAddress = localAddress; + this.ssl = ssl; + id = UUID.randomUUID(); + } + + public UUID id() { + return id; + } + + public SocketAddress getRemoteAddress() { + return remoteAddress; + } + + public SocketAddress getLocalAddress() { + return localAddress; + } + + public boolean isSsl() { + return ssl; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + RecordedHttpConnection that = (RecordedHttpConnection) o; + return ssl == that.ssl && Objects.equals(id, that.id) && Objects.equals(remoteAddress, that.remoteAddress) + && Objects.equals(localAddress, that.localAddress); + } + + @Override + public int hashCode() { + return Objects.hash(id, remoteAddress, localAddress, ssl); + } +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/RecordedRequest.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/RecordedRequest.java new file mode 100644 index 00000000000..171b2bb3445 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/RecordedRequest.java @@ -0,0 +1,95 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +import io.fabric8.mockwebserver.dsl.HttpMethod; + +import java.net.Socket; +import java.util.List; +import java.util.UUID; + +public class RecordedRequest { + + private final UUID id; + private final HttpMethod method; + private final String path; + private final Headers headers; + private final Buffer body; + + public RecordedRequest(HttpMethod method, String path, Headers headers, Buffer body) { + this.id = UUID.randomUUID(); + this.method = method; + this.path = path; + this.headers = headers; + this.body = body; + } + + public UUID id() { + return id; + } + + public String getPath() { + return path; + } + + /** + * Compatibility layer for OkHttp + * + * @return a String with the HTTP method. + */ + public String getMethod() { + return method.toString(); + } + + public HttpMethod method() { + return method; + } + + public Buffer getBody() { + return body; + } + + public String getUtf8Body() { + return getBody() != null ? getBody().readUtf8() : null; + } + + public Headers getHeaders() { + return headers; + } + + public String getHeader(String key) { + return headers.header(key); + } + + /** + * Compatibility layer for OkHttp + */ + public RecordedRequest(String requestLine, Headers headers, List chunkSizes, long bodySize, Buffer body, + int sequenceNumber, Socket socket) { + this(extractMethod(requestLine), extractPath(requestLine), headers, body); + } + + private static HttpMethod extractMethod(String requestLine) { + return HttpMethod.valueOf(requestLine.split(" ")[0]); + } + + private static String extractPath(String requestLine) { + final int methodEnd = requestLine.indexOf(' '); + final int pathEnd = requestLine.indexOf(' ', methodEnd + 1); + return requestLine.substring(methodEnd + 1, pathEnd); + } + +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/Response.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/Response.java new file mode 100644 index 00000000000..9ea278e0120 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/Response.java @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +public interface Response { + + String getStatus(); + + int code(); + + Headers getHeaders(); + + Buffer getBody(); + + WebSocketListener getWebSocketListener(); +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/WebSocket.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/WebSocket.java new file mode 100644 index 00000000000..e47a9c07128 --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/WebSocket.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +public interface WebSocket { + RecordedRequest request(); + + boolean send(String text); + + boolean send(byte[] bytes); + + /** + * Compatibility layer for OkHttp + */ + default boolean send(ByteString bytes) { + return send(bytes.toByteArray()); + } + + boolean close(int code, String reason); +} diff --git a/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/WebSocketListener.java b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/WebSocketListener.java new file mode 100644 index 00000000000..fd3322bf00c --- /dev/null +++ b/junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/http/WebSocketListener.java @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http; + +public abstract class WebSocketListener { + + /** + * Invoked when a web socket has been accepted by the remote peer and may begin transmitting + * messages. + */ + public void onOpen(WebSocket webSocket, Response response) { + } + + public void onMessage(WebSocket webSocket, String text) { + } + + public void onMessage(WebSocket webSocket, ByteString bytes) { + } + + /** + * Invoked when the remote peer has indicated that no more incoming messages will be + * transmitted. + */ + public void onClosing(WebSocket webSocket, int code, String reason) { + } + + /** + * Invoked when both peers have indicated that no more messages will be transmitted and the + * connection has been successfully released. No further calls to this listener will be made. + */ + public void onClosed(WebSocket webSocket, int code, String reason) { + } + + /** + * Invoked when a web socket has been closed due to an error reading from or writing to the + * network. Both outgoing and incoming messages may have been lost. No further calls to this + * listener will be made. + */ + public void onFailure(WebSocket webSocket, Throwable error, Response response) { + } +} diff --git a/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/http/MediaTypeTest.groovy b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/http/MediaTypeTest.groovy new file mode 100644 index 00000000000..bd72026c134 --- /dev/null +++ b/junit/mockwebserver/src/test/groovy/io/fabric8/mockwebserver/http/MediaTypeTest.groovy @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2015 Red Hat, 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 io.fabric8.mockwebserver.http + +import spock.lang.Specification + +class MediaTypeTest extends Specification { + + def "parse gets type"() { + when: + def mediaType = MediaType.parse("text/plain;boundary=foo;charset=utf-8") + then: + assert mediaType.type() == "text" + } + + def "parse gets subtype"() { + when: + def mediaType = MediaType.parse("text/plain;boundary=foo;charset=utf-8") + then: + assert mediaType.subtype() == "plain" + } + + def "parse gets charset"() { + when: + def mediaType = MediaType.parse("text/plain;boundary=foo;charset=utf-8") + then: + assert mediaType.charset() == "utf-8" + } + + def "parse preserves mediaType"() { + when: + def mediaType = MediaType.parse("text/plain;boundary=foo;charset=utf-8") + then: + assert mediaType.toString() == "text/plain;boundary=foo;charset=utf-8" + } +}