org.openqa.selenium.remote.http.netty.NettyDomainSocketClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of selenium-remote-driver Show documentation
Show all versions of selenium-remote-driver Show documentation
Selenium automates browsers. That's it! What you do with that power is entirely up to you.
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you 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 org.openqa.selenium.remote.http.netty;
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.openqa.selenium.remote.http.Contents.bytes;
import static org.openqa.selenium.remote.http.Contents.utf8String;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import com.google.common.io.ByteStreams;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollDomainSocketChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueDomainSocketChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.unix.DomainSocketAddress;
import io.netty.channel.unix.UnixChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpVersion;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.remote.http.AddSeleniumUserAgent;
import org.openqa.selenium.remote.http.ClientConfig;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
import org.openqa.selenium.remote.http.RemoteCall;
import org.openqa.selenium.remote.http.WebSocket;
class NettyDomainSocketClient extends RemoteCall implements HttpClient {
private final EventLoopGroup eventLoopGroup;
private final Class extends Channel> channelClazz;
private final String path;
public NettyDomainSocketClient(ClientConfig config) {
super(config);
URI uri = config.baseUri();
Require.argument("URI scheme", uri.getScheme()).equalTo("unix");
if (Epoll.isAvailable()) {
this.eventLoopGroup = new EpollEventLoopGroup();
this.channelClazz = EpollDomainSocketChannel.class;
} else if (KQueue.isAvailable()) {
this.eventLoopGroup = new KQueueEventLoopGroup();
this.channelClazz = KQueueDomainSocketChannel.class;
} else {
throw new IllegalStateException("No native library for unix domain sockets is available");
}
this.path = uri.getPath();
}
@Override
public HttpResponse execute(HttpRequest req) throws UncheckedIOException {
Require.nonNull("Request to send", req);
AtomicReference outRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
Channel channel = createChannel(outRef, latch);
StringBuilder uri = new StringBuilder(req.getUri());
List queryPairs = new ArrayList<>();
req.getQueryParameterNames()
.forEach(
name ->
req.getQueryParameters(name)
.forEach(
value -> {
try {
queryPairs.add(
URLEncoder.encode(name, UTF_8.toString())
+ "="
+ URLEncoder.encode(value, UTF_8.toString()));
} catch (UnsupportedEncodingException e) {
Thread.currentThread().interrupt();
throw new UncheckedIOException(e);
}
}));
if (!queryPairs.isEmpty()) {
uri.append("?");
Joiner.on('&').appendTo(uri, queryPairs);
}
byte[] bytes = bytes(req.getContent());
DefaultFullHttpRequest fullRequest =
new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1,
HttpMethod.valueOf(req.getMethod().toString()),
uri.toString(),
Unpooled.wrappedBuffer(bytes));
req.forEachHeader(fullRequest.headers()::add);
if (req.getHeader("User-Agent") == null) {
fullRequest.headers().set("User-Agent", AddSeleniumUserAgent.USER_AGENT);
}
fullRequest.headers().set(HttpHeaderNames.HOST, "localhost");
fullRequest.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
fullRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH, bytes.length);
ChannelFuture future = channel.writeAndFlush(fullRequest);
try {
future.get();
channel.closeFuture().sync();
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw new UncheckedIOException(new IOException(e));
}
try {
if (!latch.await(getConfig().readTimeout().toMillis(), MILLISECONDS)) {
throw new UncheckedIOException(new IOException("Timed out waiting for response"));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
return outRef.get();
}
@Override
public WebSocket openSocket(HttpRequest request, WebSocket.Listener listener) {
throw new UnsupportedOperationException("openSocket");
}
private Channel createChannel(AtomicReference outRef, CountDownLatch latch) {
Bootstrap bootstrap =
new Bootstrap()
.group(eventLoopGroup)
.channel(channelClazz)
.handler(
new ChannelInitializer() {
@Override
public void initChannel(UnixChannel ch) {
ch.pipeline()
.addLast(new HttpClientCodec())
.addLast(new HttpContentDecompressor())
.addLast(new HttpObjectAggregator(Integer.MAX_VALUE))
.addLast(
new SimpleChannelInboundHandler() {
@Override
public void channelRead0(
ChannelHandlerContext ctx, FullHttpResponse msg) {
HttpResponse res =
new HttpResponse().setStatus(msg.status().code());
msg.headers()
.forEach(
entry -> res.addHeader(entry.getKey(), entry.getValue()));
try (InputStream is = new ByteBufInputStream(msg.content());
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
ByteStreams.copy(is, bos);
res.setContent(bytes(bos.toByteArray()));
outRef.set(res);
latch.countDown();
} catch (IOException e) {
outRef.set(
new HttpResponse()
.setStatus(HTTP_INTERNAL_ERROR)
.setContent(
utf8String(Throwables.getStackTraceAsString(e))));
latch.countDown();
}
}
@Override
public void exceptionCaught(
ChannelHandlerContext ctx, Throwable cause) {
outRef.set(
new HttpResponse()
.setStatus(HTTP_INTERNAL_ERROR)
.setContent(
utf8String(Throwables.getStackTraceAsString(cause))));
latch.countDown();
}
});
}
});
try {
return bootstrap.connect(new DomainSocketAddress(path)).sync().channel();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy