# 方式一
实现ClientHttpRequestFactory
# 依赖
利用了junixsocket
这个 native 库以及其对 socket 的封装
<dependency>
<groupId>com.kohlschutter.junixsocket</groupId>
<artifactId>junixsocket-core</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>com.kohlschutter.junixsocket</groupId>
<artifactId>junixsocket-rmi</artifactId>
<version>2.1.2</version>
</dependency>
# 实现
# org.springframework.http.client
新建包org.springframework.http.client
,这是因为使用了具有默认访问权限的一些Abstract
类
# JnrClientHttpRequestFactory
在包内新建
JnrClientHttpRequestFactory
类,实现ClientHttpRequestFactory
接口新建了具有制定一个
sock
文件的构造器,并转换成AFUNIXSocketAddress
类,该类为InetSocketAddress
的子类实现
createRequest
方法,在该方法创建一个socket
连接,并返回JnrBufferingClientHttpRequest
(下一步将创建)
public class JnrClientHttpRequestFactory implements ClientHttpRequestFactory {
private AFUNIXSocketAddress address;
public JnrClientHttpRequestFactory(File socketFile) throws IOException {
this.address = new AFUNIXSocketAddress(socketFile);
}
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
AFUNIXSocket sock = AFUNIXSocket.connectTo(address);
return new JnrBufferingClientHttpRequest(sock, uri, httpMethod);
}
}
# JnrBufferingClientHttpRequest
同样在包内新建
JnrBufferingClientHttpRequest
类,继承AbstractBufferingClientHttpRequest
这里创建了三个变量储存构造器传来的参数。分别是
AFUNIXSocket
、URI
、HttpMethod
实现
executeInternal
方法,在这里打印请求实现
getMethodValue
、getURI
private AFUNIXSocket afunixSocket;
private URI uri;
private HttpMethod httpMethod;
public JnrBufferingClientHttpRequest(AFUNIXSocket afunixSocket, URI uri, HttpMethod httpMethod) {
this.afunixSocket = afunixSocket;
this.uri = uri;
this.httpMethod = httpMethod;
}
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
OutputStream outputStream = afunixSocket.getOutputStream();
PrintWriter writer = new PrintWriter(outputStream);
writer.println(String.format("%s %s HTTP/1.1", httpMethod, uri));
headers
.forEach((key, values) -> {
if (values.isEmpty()) return;
writer.append(key).append(": ");
if (values.size() == 1)
writer.append(values.get(0));
else
values.forEach(value -> writer.append(value).append("; "));
writer.append("\\r");
});
writer.println("Host: http");
writer.println("");
outputStream.write(bufferedOutput);
writer.flush();
writer.close();
return new JnrClientHttpResponse(afunixSocket);
}
@Override
public String getMethodValue() {
return httpMethod.name();
}
@Override
public URI getURI() {
return uri;
}
# JnrClientHttpResponse
- 包内创建
JnrClientHttpResponse
类,继承AbstractClientHttpResponse
- 创建
InputStream
、HttpHeaders
、statusCode
、statusTest
等 response 字段 - 在构造器阶段读取响应
- 首先读取请求行
- 然后是解析请求头
- 此时
Inputstream
将标记到剩下的请求体部分
- 其中
readLine
为读取一行数据,readResponseHead
为读取请求行 - 实现
getRawStatusCode
、getStatusText
、getHeaders
方法 close
方法参考原生SimpleClientHttpResponse
getBody
方法返回已经标记到请求体的inputStream
private InputStream inputStream;
private HttpHeaders httpHeaders;
private int statusCode = -1;
private String statusTest = "UNKNOWN";
JnrClientHttpResponse(AFUNIXSocket afunixSocket) throws IOException {
this.inputStream = afunixSocket.getInputStream();
readResponseHead(readLine(inputStream));
httpHeaders = new HttpHeaders();
String line;
while (!(line = readLine(inputStream)).isEmpty()) {
String[] strings = line.split(" ", 2);
httpHeaders.add(strings[0], strings[1]);
}
}
@Override
public int getRawStatusCode() throws IOException {
return statusCode;
}
@Override
public String getStatusText() throws IOException {
return statusTest;
}
@Override
public void close() {
try {
StreamUtils.drain(this.inputStream);
this.inputStream.close();
} catch (Exception ex) {
// ignore
}
}
@Override
public InputStream getBody() throws IOException {
return inputStream;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
private void readResponseHead(String line) {
String[] strings = line.split(" ", 3);
statusCode = Integer.valueOf(strings[1]);
statusTest = strings[2];
}
private String readLine(InputStream inputStream) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
int ch;
while ((ch = inputStream.read()) != -1) {
if (ch == '\\r') {
//noinspection ResultOfMethodCallIgnored
inputStream.read();
return stringBuilder.toString();
}
stringBuilder.append((char) ch);
}
return stringBuilder.toString();
}
# 测试
编写测试代码
@Test
public void contextLoads() throws IOException {
RestTemplate restTemplate = new RestTemplate(new JnrClientHttpRequestFactory(Paths.get("/var/run/docker.sock").toFile()));
HttpEntity<String> object = restTemplate.getForEntity("/v1.24/images/json", String.class);
log.info(objectMapper.writeValueAsString(object));
}
得到响应
2019-02-05 22:35:57.730 INFO 34943 --- [ main] p.a.t.jnrtest.JnrTestApplicationTests : {"headers":{"Date:":["Tue, 05 Feb 2019 14:35:57 GMT"],"Docker-Experimental:":["false"],"Server:":["Docker/18.09.1 (linux)"],"Content-Length:":["327"],"Ostype:":["linux"],"Api-Version:":["1.39"],"Content-Type:":["application/json"]},"body":"[{\\\\"Containers\\\\":-1,\\\\"Created\\\\":1548886792,\\\\"Id\\\\":\\\\"sha256:caf27325b298a6730837023a8a342699c8b7b388b8d878966b064a1320043019\\\\",\\\\"Labels\\\\":null,\\\\"ParentId\\\\":\\\\"\\\\",\\\\"RepoDigests\\\\":[\\\\"alpine@sha256:b3dbf31b77fd99d9c08f780ce6f5282aba076d70a513a8be859d8d3a4d0c92b8\\\\"],\\\\"RepoTags\\\\":[\\\\"alpine:latest\\\\"],\\\\"SharedSize\\\\":-1,\\\\"Size\\\\":5529164,\\\\"VirtualSize\\\\":5529164}]\\","statusCodeValue":200,"statusCode":"OK"}
# 方式二
# 依赖
OkHttp
<dependency>
<groupId>com.kohlschutter.junixsocket</groupId>
<artifactId>junixsocket-core</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>com.kohlschutter.junixsocket</groupId>
<artifactId>junixsocket-rmi</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.12.1</version>
</dependency>
# 实现
# JnrAFUNIXSocketFactory
- 新建该类,继承
AFUNIXSocketFactory
- 创建构造器,如方式一中提供了一个
AFUNIXSocketAddress
变量 - 实现
createSocket
此处需要注意一定要使用super.createSocket()
以绑定自身factory
不然解析URL会出错 - 然后
connect
到 UNIX Socket 中获得fd
文件描述符,并将其返回 - 实现
addressFromHost
直接返回 socket 地址,因为没有host
和port
属性,参数可以直接忽略
private AFUNIXSocketAddress address;
public JnrAFUNIXSocketFactory(File socketFile) throws IOException {
address = new AFUNIXSocketAddress(socketFile);
}
@Override
public Socket createSocket() throws IOException {
Socket socket = super.createSocket();
socket.connect(address);
return socket;
}
@Override
protected AFUNIXSocketAddress addressFromHost(String s, int i) throws IOException {
return address;
}
# 测试
这里使用了OkHttpClient
的Builder
以便使用自定义的socketFactory
然后在RestTemplate
中指定ClientHttpRequestFactory
注意这一次 URI 需要加上schema
如http
,host
也需要指定例如localhost
,以通过 URL 的校验,否则会报错
不过使用中会被忽略,因为会直接使用指定的socketFile
@Test
public void okHttp() throws IOException {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
OkHttpClient okHttpClient = builder
.socketFactory(new JnrAFUNIXSocketFactory(Paths.get("/var/run/docker.sock").toFile()))
.build();
OkHttp3ClientHttpRequestFactory okHttp3ClientHttpRequestFactory =
new OkHttp3ClientHttpRequestFactory(okHttpClient);
RestTemplate restTemplate = new RestTemplate(okHttp3ClientHttpRequestFactory);
HttpEntity<String> object = restTemplate.getForEntity("http://localhost/v1.24/images/json", String.class);
log.info(objectMapper.writeValueAsString(object));
}
得到结果
2019-02-05 22:58:01.957 INFO 35177 --- [ main] p.a.t.jnrtest.JnrTestApplicationTests : {"headers":{"Api-Version":["1.39"],"Ostype":["linux"],"Content-Type":["application/json"],"Docker-Experimental":["false"],"Content-Length":["327"],"Date":["Tue, 05 Feb 2019 14:58:01 GMT"],"Server":["Docker/18.09.1 (linux)"]},"body":"[{\\\\"Containers\\\\":-1,\\\\"Created\\\\":1548886792,\\\\"Id\\\\":\\\\"sha256:caf27325b298a6730837023a8a342699c8b7b388b8d878966b064a1320043019\\\\",\\\\"Labels\\\\":null,\\\\"ParentId\\\\":\\\\"\\\\",\\\\"RepoDigests\\\\":[\\\\"alpine@sha256:b3dbf31b77fd99d9c08f780ce6f5282aba076d70a513a8be859d8d3a4d0c92b8\\\\"],\\\\"RepoTags\\\\":[\\\\"alpine:latest\\\\"],\\\\"SharedSize\\\\":-1,\\\\"Size\\\\":5529164,\\\\"VirtualSize\\\\":5529164}]\\","statusCodeValue":200,"statusCode":"OK"}