mirror of https://github.com/apache/nifi.git
NIFI-10244 Added nifi-web-client-api and implementation
- Added nifi-web-client implementation based on OkHttp - Added WebClientServiceProvider Controller Service interface and implementation - Corrected comments and added unmodifiableMap wrapper - Added getHeaderNames() and corrected ProxyContext comments This closes #6268 Signed-off-by: Paul Grey <greyp@apache.org>
This commit is contained in:
parent
a3a7b2fcfd
commit
864036674e
|
@ -844,6 +844,12 @@ language governing permissions and limitations under the License. -->
|
|||
<version>1.18.0-SNAPSHOT</version>
|
||||
<type>nar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-client-provider-service-nar</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
<type>nar</type>
|
||||
</dependency>
|
||||
<!-- dependencies for jaxb/activation/annotation for running NiFi on Java 11 -->
|
||||
<!-- TODO: remove these once minimum Java version is 11 -->
|
||||
<dependency>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF 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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-commons</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-web-client-api</artifactId>
|
||||
<description>Abstracts standard HTTP client operations without depending on a specific HTTP library</description>
|
||||
</project>
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.api;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* HTTP Entity Headers supporting retrieval of single or multiple header values
|
||||
*/
|
||||
public interface HttpEntityHeaders {
|
||||
/**
|
||||
* Get First Header using specified Header Name
|
||||
*
|
||||
* @param headerName Header Name to be retrieved
|
||||
* @return First Header Value or empty when not found
|
||||
*/
|
||||
Optional<String> getFirstHeader(String headerName);
|
||||
|
||||
/**
|
||||
* Get Header Values using specified Header Name
|
||||
*
|
||||
* @param headerName Header Name to be retrieved
|
||||
* @return List of Header Values or empty when not found
|
||||
*/
|
||||
List<String> getHeader(String headerName);
|
||||
|
||||
/**
|
||||
* Get Header Names
|
||||
*
|
||||
* @return Collection of Header Names or empty when not found
|
||||
*/
|
||||
Collection<String> getHeaderNames();
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.api;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
/**
|
||||
* HTTP Request Body Specification builder
|
||||
*/
|
||||
public interface HttpRequestBodySpec extends HttpRequestHeadersSpec {
|
||||
/**
|
||||
* Set Request Body as stream
|
||||
*
|
||||
* @param inputStream Request Body stream is required
|
||||
* @param contentLength Content Length or empty when not known
|
||||
* @return HTTP Request Headers Specification builder
|
||||
*/
|
||||
HttpRequestHeadersSpec body(InputStream inputStream, OptionalLong contentLength);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.api;
|
||||
|
||||
/**
|
||||
* HTTP Request Headers Specification builder
|
||||
*/
|
||||
public interface HttpRequestHeadersSpec {
|
||||
/**
|
||||
* Add HTTP Request Header using specified name and value
|
||||
*
|
||||
* @param headerName HTTP Header Name
|
||||
* @param headerValue HTTP Header Value
|
||||
* @return HTTP Request Body Specification builder
|
||||
*/
|
||||
HttpRequestBodySpec header(String headerName, String headerValue);
|
||||
|
||||
/**
|
||||
* Execute HTTP Request and retrieve HTTP Response
|
||||
*
|
||||
* @return HTTP Response Entity
|
||||
*/
|
||||
HttpResponseEntity retrieve();
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.api;
|
||||
|
||||
/**
|
||||
* HTTP Request Method
|
||||
*/
|
||||
public interface HttpRequestMethod {
|
||||
/**
|
||||
* Get HTTP Method
|
||||
*
|
||||
* @return HTTP Method
|
||||
*/
|
||||
String getMethod();
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.api;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* HTTP Request URI Specification builder
|
||||
*/
|
||||
public interface HttpRequestUriSpec {
|
||||
/**
|
||||
* Create HTTP Request Body builder using specified Request URI
|
||||
*
|
||||
* @param uri Request URI
|
||||
* @return HTTP Request Body Specification builder
|
||||
*/
|
||||
HttpRequestBodySpec uri(URI uri);
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.api;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* HTTP Response Entity extends Closeable to handle closing Response Body
|
||||
*/
|
||||
public interface HttpResponseEntity extends Closeable {
|
||||
/**
|
||||
* Get HTTP Response Status Code
|
||||
*
|
||||
* @return HTTP Response Status Code
|
||||
*/
|
||||
int statusCode();
|
||||
|
||||
/**
|
||||
* Get HTTP Response Headers
|
||||
*
|
||||
* @return HTTP Response Headers
|
||||
*/
|
||||
HttpEntityHeaders headers();
|
||||
|
||||
/**
|
||||
* Get HTTP Response Body stream
|
||||
*
|
||||
* @return HTTP Response Body stream can be empty
|
||||
*/
|
||||
InputStream body();
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.api;
|
||||
|
||||
/**
|
||||
* Enumeration of Standard HTTP Response Status Codes
|
||||
*/
|
||||
public enum HttpResponseStatus {
|
||||
OK(200),
|
||||
|
||||
CREATED(201),
|
||||
|
||||
ACCEPTED(202),
|
||||
|
||||
NO_CONTENT(204),
|
||||
|
||||
MOVED_PERMANENTLY(301),
|
||||
|
||||
BAD_REQUEST(400),
|
||||
|
||||
UNAUTHORIZED(401),
|
||||
|
||||
FORBIDDEN(403),
|
||||
|
||||
NOT_FOUND(404),
|
||||
|
||||
METHOD_NOT_ALLOWED(405),
|
||||
|
||||
PROXY_AUTHENTICATION_REQUIRED(407),
|
||||
|
||||
INTERNAL_SERVER_ERROR(500),
|
||||
|
||||
SERVICE_UNAVAILABLE(503);
|
||||
|
||||
private final int code;
|
||||
|
||||
HttpResponseStatus(final int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.api;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* HTTP URI Builder supports construction of a URI using component elements
|
||||
*/
|
||||
public interface HttpUriBuilder {
|
||||
/**
|
||||
* Build URI based on current component elements
|
||||
*
|
||||
* @return URI
|
||||
*/
|
||||
URI build();
|
||||
|
||||
/**
|
||||
* Set URI scheme as http or https
|
||||
*
|
||||
* @param scheme URI scheme
|
||||
* @return Builder
|
||||
*/
|
||||
HttpUriBuilder scheme(String scheme);
|
||||
|
||||
/**
|
||||
* Set URI host address
|
||||
*
|
||||
* @param host Host address
|
||||
* @return Builder
|
||||
*/
|
||||
HttpUriBuilder host(String host);
|
||||
|
||||
/**
|
||||
* Set URI port number
|
||||
*
|
||||
* @param port Port number
|
||||
* @return Builder
|
||||
*/
|
||||
HttpUriBuilder port(int port);
|
||||
|
||||
/**
|
||||
* Set path with segments encoded according to URL standard requirements
|
||||
*
|
||||
* @param encodedPath URL-encoded path
|
||||
* @return Builder
|
||||
*/
|
||||
HttpUriBuilder encodedPath(String encodedPath);
|
||||
|
||||
/**
|
||||
* Add path segment appending to current path
|
||||
*
|
||||
* @param pathSegment Path segment
|
||||
* @return Builder
|
||||
*/
|
||||
HttpUriBuilder addPathSegment(String pathSegment);
|
||||
|
||||
/**
|
||||
* Add query parameter using specified name and value
|
||||
*
|
||||
* @param name Query parameter name
|
||||
* @param value Query parameter value can be null
|
||||
* @return Builder
|
||||
*/
|
||||
HttpUriBuilder addQueryParameter(String name, String value);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.api;
|
||||
|
||||
/**
|
||||
* Enumeration of standard HTTP Request Methods
|
||||
*/
|
||||
public enum StandardHttpRequestMethod implements HttpRequestMethod {
|
||||
DELETE,
|
||||
|
||||
GET,
|
||||
|
||||
PATCH,
|
||||
|
||||
POST,
|
||||
|
||||
PUT;
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return name();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.api;
|
||||
|
||||
/**
|
||||
* Service abstraction for HTTP client operations
|
||||
*/
|
||||
public interface WebClientService {
|
||||
/**
|
||||
* Create HTTP Request builder starting with specified HTTP Request Method
|
||||
*
|
||||
* @param requestMethod HTTP Request Method
|
||||
* @return HTTP Request URI Specification builder
|
||||
*/
|
||||
HttpRequestUriSpec method(HttpRequestMethod requestMethod);
|
||||
|
||||
/**
|
||||
* Create HTTP Request builder starting with HTTP DELETE
|
||||
*
|
||||
* @return HTTP Request URI Specification builder
|
||||
*/
|
||||
HttpRequestUriSpec delete();
|
||||
|
||||
/**
|
||||
* Create HTTP Request builder starting with HTTP GET
|
||||
*
|
||||
* @return HTTP Request URI Specification builder
|
||||
*/
|
||||
HttpRequestUriSpec get();
|
||||
|
||||
/**
|
||||
* Create HTTP Request builder starting with HTTP PATCH
|
||||
*
|
||||
* @return HTTP Request URI Specification builder
|
||||
*/
|
||||
HttpRequestUriSpec patch();
|
||||
|
||||
/**
|
||||
* Create HTTP Request builder starting with HTTP POST
|
||||
*
|
||||
* @return HTTP Request URI Specification builder
|
||||
*/
|
||||
HttpRequestUriSpec post();
|
||||
|
||||
/**
|
||||
* Create HTTP Request builder starting with HTTP PUT
|
||||
*
|
||||
* @return HTTP Request URI Specification builder
|
||||
*/
|
||||
HttpRequestUriSpec put();
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.api;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Web Client Service Exception provides a generalized wrapper for HTTP communication failures
|
||||
*/
|
||||
public class WebClientServiceException extends RuntimeException {
|
||||
/**
|
||||
* Web Service Client Exception with standard HTTP request properties
|
||||
*
|
||||
* @param message Failure message
|
||||
* @param cause Failure cause
|
||||
* @param uri HTTP Request URI
|
||||
* @param httpRequestMethod HTTP Request Method
|
||||
*/
|
||||
public WebClientServiceException(
|
||||
final String message,
|
||||
final Throwable cause,
|
||||
final URI uri,
|
||||
final HttpRequestMethod httpRequestMethod
|
||||
) {
|
||||
super(getMessage(message, uri, httpRequestMethod), cause);
|
||||
}
|
||||
|
||||
private static String getMessage(final String message, final URI uri, final HttpRequestMethod httpRequestMethod) {
|
||||
return String.format("%s HTTP Method [%s] URI [%s]", message, httpRequestMethod, uri);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF 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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-commons</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-web-client</artifactId>
|
||||
<description>Standard implementation of nifi-web-client-api using OkHttp</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-client-api</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client;
|
||||
|
||||
import okhttp3.Authenticator;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.Route;
|
||||
|
||||
/**
|
||||
* OkHttp Authenticator supporting Proxy Authentication using HTTP Basic credentials
|
||||
*/
|
||||
class BasicProxyAuthenticator implements Authenticator {
|
||||
private static final String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
|
||||
|
||||
private final String credentials;
|
||||
|
||||
BasicProxyAuthenticator(final String credentials) {
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request authenticate(final Route route, final Response response) {
|
||||
return response.request()
|
||||
.newBuilder()
|
||||
.header(PROXY_AUTHORIZATION_HEADER, credentials)
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.RequestBody;
|
||||
import okio.BufferedSink;
|
||||
import okio.Okio;
|
||||
import okio.Source;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* OkHttp Request Body implementation based on an InputStream
|
||||
*/
|
||||
class InputStreamRequestBody extends RequestBody {
|
||||
private final InputStream inputStream;
|
||||
|
||||
private final long contentLength;
|
||||
|
||||
InputStreamRequestBody(final InputStream inputStream, final long contentLength) {
|
||||
this.inputStream = inputStream;
|
||||
this.contentLength = contentLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() {
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(final BufferedSink bufferedSink) throws IOException {
|
||||
final Source source = Okio.source(inputStream);
|
||||
bufferedSink.writeAll(source);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client;
|
||||
|
||||
import org.apache.nifi.web.client.api.HttpEntityHeaders;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Standard implementation of HTTP Entity Headers for Standard Web Client Service
|
||||
*/
|
||||
class StandardHttpEntityHeaders implements HttpEntityHeaders {
|
||||
private final Map<String, List<String>> headers;
|
||||
|
||||
StandardHttpEntityHeaders(final Map<String, List<String>> headers) {
|
||||
this.headers = Collections.unmodifiableMap(headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getFirstHeader(final String headerName) {
|
||||
final List<String> values = getHeader(headerName);
|
||||
return values.stream().findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeader(final String headerName) {
|
||||
Objects.requireNonNull(headerName, "Header Name required");
|
||||
final List<String> values = headers.get(headerName);
|
||||
return values == null ? Collections.emptyList() : Collections.unmodifiableList(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getHeaderNames() {
|
||||
return Collections.unmodifiableSet(headers.keySet());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client;
|
||||
|
||||
import org.apache.nifi.web.client.api.HttpEntityHeaders;
|
||||
import org.apache.nifi.web.client.api.HttpResponseEntity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Standard implementation of HTTP Response Entity for Standard Web Client Service
|
||||
*/
|
||||
class StandardHttpResponseEntity implements HttpResponseEntity {
|
||||
private final int statusCode;
|
||||
|
||||
private final HttpEntityHeaders headers;
|
||||
|
||||
private final InputStream body;
|
||||
|
||||
StandardHttpResponseEntity(
|
||||
final int statusCode,
|
||||
final HttpEntityHeaders headers,
|
||||
final InputStream body
|
||||
) {
|
||||
this.statusCode = statusCode;
|
||||
this.headers = headers;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int statusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpEntityHeaders headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream body() {
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
body.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import org.apache.nifi.web.client.api.HttpUriBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Standard HTTP URI Builder based on OkHttp HttpUrl
|
||||
*/
|
||||
public class StandardHttpUriBuilder implements HttpUriBuilder {
|
||||
private final HttpUrl.Builder builder;
|
||||
|
||||
public StandardHttpUriBuilder() {
|
||||
this.builder = new HttpUrl.Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI build() {
|
||||
final HttpUrl httpUrl = builder.build();
|
||||
return httpUrl.uri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpUriBuilder scheme(final String scheme) {
|
||||
Objects.requireNonNull(scheme, "Scheme required");
|
||||
builder.scheme(scheme);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpUriBuilder host(final String host) {
|
||||
Objects.requireNonNull(host, "Host required");
|
||||
builder.host(host);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpUriBuilder port(int port) {
|
||||
builder.port(port);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpUriBuilder encodedPath(final String encodedPath) {
|
||||
builder.encodedPath(encodedPath);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpUriBuilder addPathSegment(final String pathSegment) {
|
||||
Objects.requireNonNull(pathSegment, "Path segment required");
|
||||
builder.addPathSegment(pathSegment);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpUriBuilder addQueryParameter(final String name, final String value) {
|
||||
Objects.requireNonNull(name, "Parameter name required");
|
||||
builder.addQueryParameter(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Credentials;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
import org.apache.nifi.web.client.api.HttpEntityHeaders;
|
||||
import org.apache.nifi.web.client.api.HttpRequestBodySpec;
|
||||
import org.apache.nifi.web.client.api.HttpRequestHeadersSpec;
|
||||
import org.apache.nifi.web.client.api.HttpRequestMethod;
|
||||
import org.apache.nifi.web.client.api.HttpRequestUriSpec;
|
||||
import org.apache.nifi.web.client.api.HttpResponseEntity;
|
||||
import org.apache.nifi.web.client.api.StandardHttpRequestMethod;
|
||||
import org.apache.nifi.web.client.api.WebClientService;
|
||||
import org.apache.nifi.web.client.api.WebClientServiceException;
|
||||
import org.apache.nifi.web.client.proxy.ProxyContext;
|
||||
import org.apache.nifi.web.client.redirect.RedirectHandling;
|
||||
import org.apache.nifi.web.client.ssl.SSLSocketFactoryProvider;
|
||||
import org.apache.nifi.web.client.ssl.StandardSSLSocketFactoryProvider;
|
||||
import org.apache.nifi.web.client.ssl.TlsContext;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Proxy;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
/**
|
||||
* Standard implementation of Web Client Service using OkHttp
|
||||
*/
|
||||
public class StandardWebClientService implements WebClientService {
|
||||
private static final byte[] EMPTY_BYTES = new byte[0];
|
||||
|
||||
private static final SSLSocketFactoryProvider sslSocketFactoryProvider = new StandardSSLSocketFactoryProvider();
|
||||
|
||||
private OkHttpClient okHttpClient;
|
||||
|
||||
/**
|
||||
* Standard Web Client Service constructor creates OkHttpClient using default settings
|
||||
*/
|
||||
public StandardWebClientService() {
|
||||
okHttpClient = new OkHttpClient.Builder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set timeout for initial socket connection
|
||||
*
|
||||
* @param connectTimeout Connect Timeout
|
||||
*/
|
||||
public void setConnectTimeout(final Duration connectTimeout) {
|
||||
Objects.requireNonNull(connectTimeout, "Connect Timeout required");
|
||||
okHttpClient = okHttpClient.newBuilder().connectTimeout(connectTimeout).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set timeout for reading responses from socket connection
|
||||
*
|
||||
* @param readTimeout Read Timeout
|
||||
*/
|
||||
public void setReadTimeout(final Duration readTimeout) {
|
||||
Objects.requireNonNull(readTimeout, "Read Timeout required");
|
||||
okHttpClient = okHttpClient.newBuilder().readTimeout(readTimeout).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set timeout for writing requests to socket connection
|
||||
*
|
||||
* @param writeTimeout Write Timeout
|
||||
*/
|
||||
public void setWriteTimeout(final Duration writeTimeout) {
|
||||
Objects.requireNonNull(writeTimeout, "Write Timeout required");
|
||||
okHttpClient = okHttpClient.newBuilder().writeTimeout(writeTimeout).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Proxy Context configuration for socket communication
|
||||
*
|
||||
* @param proxyContext Proxy Context configuration
|
||||
*/
|
||||
public void setProxyContext(final ProxyContext proxyContext) {
|
||||
Objects.requireNonNull(proxyContext, "Proxy Context required");
|
||||
final Proxy proxy = Objects.requireNonNull(proxyContext.getProxy(), "Proxy required");
|
||||
okHttpClient = okHttpClient.newBuilder().proxy(proxy).build();
|
||||
|
||||
final Optional<String> proxyUsername = proxyContext.getUsername();
|
||||
if (proxyUsername.isPresent()) {
|
||||
final String username = proxyUsername.get();
|
||||
final String password = proxyContext.getPassword().orElseThrow(() -> new IllegalArgumentException("Proxy password required"));
|
||||
final String credentials = Credentials.basic(username, password);
|
||||
final BasicProxyAuthenticator proxyAuthenticator = new BasicProxyAuthenticator(credentials);
|
||||
okHttpClient = okHttpClient.newBuilder().proxyAuthenticator(proxyAuthenticator).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Redirect Handling strategy
|
||||
*
|
||||
* @param redirectHandling Redirect Handling strategy
|
||||
*/
|
||||
public void setRedirectHandling(final RedirectHandling redirectHandling) {
|
||||
Objects.requireNonNull(redirectHandling, "Redirect Handling required");
|
||||
final boolean followRedirects = RedirectHandling.FOLLOWED == redirectHandling;
|
||||
okHttpClient = okHttpClient.newBuilder().followRedirects(followRedirects).followSslRedirects(followRedirects).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set TLS Context overrides system default TLS settings for HTTPS communication
|
||||
*
|
||||
* @param tlsContext TLS Context
|
||||
*/
|
||||
public void setTlsContext(final TlsContext tlsContext) {
|
||||
Objects.requireNonNull(tlsContext, "TLS Context required");
|
||||
final X509TrustManager trustManager = Objects.requireNonNull(tlsContext.getTrustManager(), "Trust Manager required");
|
||||
final SSLSocketFactory sslSocketFactory = sslSocketFactoryProvider.getSocketFactory(tlsContext);
|
||||
okHttpClient = okHttpClient.newBuilder().sslSocketFactory(sslSocketFactory, trustManager).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HTTP Request builder starting with specified HTTP Request Method
|
||||
*
|
||||
* @param httpRequestMethod HTTP Request Method required
|
||||
* @return HTTP Request URI Specification builder
|
||||
*/
|
||||
@Override
|
||||
public HttpRequestUriSpec method(final HttpRequestMethod httpRequestMethod) {
|
||||
Objects.requireNonNull(httpRequestMethod, "HTTP Request Method required");
|
||||
return new StandardHttpRequestUriSpec(httpRequestMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HTTP Request builder starting with HTTP DELETE
|
||||
*
|
||||
* @return HTTP Request URI Specification builder
|
||||
*/
|
||||
@Override
|
||||
public HttpRequestUriSpec delete() {
|
||||
return method(StandardHttpRequestMethod.DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HTTP Request builder starting with HTTP GET
|
||||
*
|
||||
* @return HTTP Request URI Specification builder
|
||||
*/
|
||||
@Override
|
||||
public HttpRequestUriSpec get() {
|
||||
return method(StandardHttpRequestMethod.GET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HTTP Request builder starting with HTTP PATCH
|
||||
*
|
||||
* @return HTTP Request URI Specification builder
|
||||
*/
|
||||
@Override
|
||||
public HttpRequestUriSpec patch() {
|
||||
return method(StandardHttpRequestMethod.PATCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HTTP Request builder starting with HTTP POST
|
||||
*
|
||||
* @return HTTP Request URI Specification builder
|
||||
*/
|
||||
public HttpRequestUriSpec post() {
|
||||
return method(StandardHttpRequestMethod.POST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HTTP Request builder starting with HTTP PUT
|
||||
*
|
||||
* @return HTTP Request URI Specification builder
|
||||
*/
|
||||
public HttpRequestUriSpec put() {
|
||||
return method(StandardHttpRequestMethod.PUT);
|
||||
}
|
||||
|
||||
class StandardHttpRequestUriSpec implements HttpRequestUriSpec {
|
||||
private final HttpRequestMethod httpRequestMethod;
|
||||
|
||||
StandardHttpRequestUriSpec(final HttpRequestMethod httpRequestMethod) {
|
||||
this.httpRequestMethod = httpRequestMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequestBodySpec uri(final URI uri) {
|
||||
Objects.requireNonNull(uri, "URI required");
|
||||
return new StandardHttpRequestBodySpec(httpRequestMethod, uri);
|
||||
}
|
||||
}
|
||||
|
||||
class StandardHttpRequestBodySpec implements HttpRequestBodySpec {
|
||||
private static final long UNKNOWN_CONTENT_LENGTH = -1;
|
||||
|
||||
private final HttpRequestMethod httpRequestMethod;
|
||||
|
||||
private final URI uri;
|
||||
|
||||
private final Headers.Builder headersBuilder;
|
||||
|
||||
private long contentLength = UNKNOWN_CONTENT_LENGTH;
|
||||
|
||||
private InputStream body;
|
||||
|
||||
StandardHttpRequestBodySpec(final HttpRequestMethod httpRequestMethod, final URI uri) {
|
||||
this.httpRequestMethod = httpRequestMethod;
|
||||
this.uri = uri;
|
||||
this.headersBuilder = new Headers.Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequestHeadersSpec body(final InputStream body, final OptionalLong contentLength) {
|
||||
this.body = Objects.requireNonNull(body, "Body required");
|
||||
this.contentLength = Objects.requireNonNull(contentLength, "Content Length required").orElse(UNKNOWN_CONTENT_LENGTH);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequestBodySpec header(final String headerName, final String headerValue) {
|
||||
Objects.requireNonNull(headerName, "Header Name required");
|
||||
Objects.requireNonNull(headerValue, "Header Value required");
|
||||
headersBuilder.add(headerName, headerValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponseEntity retrieve() {
|
||||
final Request request = getRequest();
|
||||
final Call call = okHttpClient.newCall(request);
|
||||
final Response response = execute(call);
|
||||
|
||||
final int code = response.code();
|
||||
final Headers responseHeaders = response.headers();
|
||||
final HttpEntityHeaders headers = new StandardHttpEntityHeaders(responseHeaders.toMultimap());
|
||||
final ResponseBody responseBody = response.body();
|
||||
final InputStream body = responseBody == null ? new ByteArrayInputStream(EMPTY_BYTES) : responseBody.byteStream();
|
||||
|
||||
return new StandardHttpResponseEntity(code, headers, body);
|
||||
}
|
||||
|
||||
private Response execute(final Call call) {
|
||||
try {
|
||||
return call.execute();
|
||||
} catch (final IOException e) {
|
||||
throw new WebClientServiceException("Request execution failed", e, uri, httpRequestMethod);
|
||||
}
|
||||
}
|
||||
|
||||
private Request getRequest() {
|
||||
final HttpUrl url = HttpUrl.get(uri);
|
||||
Objects.requireNonNull(url, "HTTP Request URI required");
|
||||
|
||||
final Headers headers = headersBuilder.build();
|
||||
final RequestBody requestBody = getRequestBody();
|
||||
|
||||
return new Request.Builder()
|
||||
.method(httpRequestMethod.getMethod(), requestBody)
|
||||
.url(url)
|
||||
.headers(headers)
|
||||
.build();
|
||||
}
|
||||
|
||||
private RequestBody getRequestBody() {
|
||||
final RequestBody requestBody;
|
||||
|
||||
if (body == null) {
|
||||
requestBody = null;
|
||||
} else {
|
||||
requestBody = new InputStreamRequestBody(body, contentLength);
|
||||
}
|
||||
|
||||
return requestBody;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.proxy;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Proxy Context provides information necessary to access sites through a Proxy with or without authentication
|
||||
*/
|
||||
public interface ProxyContext {
|
||||
/**
|
||||
* Get Proxy including Proxy Type and Proxy Server
|
||||
*
|
||||
* @return Proxy
|
||||
*/
|
||||
Proxy getProxy();
|
||||
|
||||
/**
|
||||
* Get Username for Proxy Authentication
|
||||
*
|
||||
* @return Username or empty when not configured
|
||||
*/
|
||||
Optional<String> getUsername();
|
||||
|
||||
/**
|
||||
* Get Password for Proxy Authentication
|
||||
*
|
||||
* @return Password or empty when not configured
|
||||
*/
|
||||
Optional<String> getPassword();
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.redirect;
|
||||
|
||||
/**
|
||||
* HTTP redirect handling strategy
|
||||
*/
|
||||
public enum RedirectHandling {
|
||||
/** Follow HTTP location returned from an HTTP 300 series status */
|
||||
FOLLOWED,
|
||||
|
||||
/** Ignore HTTP location returned from an HTTP 300 series status */
|
||||
IGNORED
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.ssl;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* SSLSocketFactory Provider
|
||||
*/
|
||||
public interface SSLSocketFactoryProvider {
|
||||
/**
|
||||
* Get SSLSocketFactory using provided TLS Context configuration
|
||||
*
|
||||
* @param tlsContext TLS Context configuration
|
||||
* @return SSLSocketFactory
|
||||
*/
|
||||
SSLSocketFactory getSocketFactory(TlsContext tlsContext);
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.ssl;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509KeyManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Standard implementation of SSLSocketFactory Provider
|
||||
*/
|
||||
public class StandardSSLSocketFactoryProvider implements SSLSocketFactoryProvider {
|
||||
/**
|
||||
* Get SSLSocketFactory defaults to system Trust Manager and allows an empty Key Manager
|
||||
*
|
||||
* @param tlsContext TLS Context configuration
|
||||
* @return SSLSocketFactory
|
||||
*/
|
||||
@Override
|
||||
public SSLSocketFactory getSocketFactory(final TlsContext tlsContext) {
|
||||
Objects.requireNonNull(tlsContext, "TLS Context required");
|
||||
final SSLContext sslContext = getSslContext(tlsContext);
|
||||
|
||||
try {
|
||||
final Optional<X509KeyManager> keyManager = tlsContext.getKeyManager();
|
||||
final KeyManager[] keyManagers = keyManager.map(x509KeyManager -> new KeyManager[]{x509KeyManager}).orElse(null);
|
||||
|
||||
final X509TrustManager trustManager = tlsContext.getTrustManager();
|
||||
final TrustManager[] trustManagers = trustManager == null ? null : new TrustManager[]{trustManager};
|
||||
|
||||
final SecureRandom secureRandom = new SecureRandom();
|
||||
sslContext.init(keyManagers, trustManagers, secureRandom);
|
||||
|
||||
return sslContext.getSocketFactory();
|
||||
} catch (final KeyManagementException e) {
|
||||
throw new IllegalArgumentException("SSLContext initialization failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private SSLContext getSslContext(final TlsContext tlsContext) {
|
||||
final String protocol = Objects.requireNonNull(tlsContext.getProtocol(), "TLS Protocol required");
|
||||
try {
|
||||
return SSLContext.getInstance(protocol);
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(String.format("SSLContext protocol [%s] not supported", protocol), e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.ssl;
|
||||
|
||||
import javax.net.ssl.X509KeyManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* TLS Context provides components necessary for TLS communication
|
||||
*/
|
||||
public interface TlsContext {
|
||||
/**
|
||||
* Get TLS Protocol
|
||||
*
|
||||
* @return TLS Protocol
|
||||
*/
|
||||
String getProtocol();
|
||||
|
||||
/**
|
||||
* Get X.509 Trust Manager
|
||||
*
|
||||
* @return X.509 Trust Manager
|
||||
*/
|
||||
X509TrustManager getTrustManager();
|
||||
|
||||
/**
|
||||
* Get X.509 Key Manager
|
||||
*
|
||||
* @return X.509 Key Manager or empty when not configured
|
||||
*/
|
||||
Optional<X509KeyManager> getKeyManager();
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client;
|
||||
|
||||
import org.apache.nifi.web.client.api.HttpUriBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class StandardHttpUriBuilderTest {
|
||||
private static final String HTTP_SCHEME = "http";
|
||||
|
||||
private static final String LOCALHOST = "localhost";
|
||||
|
||||
private static final int PORT = 8080;
|
||||
|
||||
private static final String ENCODED_PATH = "/resources/search";
|
||||
|
||||
private static final String RESOURCES_PATH_SEGMENT = "resources";
|
||||
|
||||
private static final String PARAMETER_NAME = "search";
|
||||
|
||||
private static final String PARAMETER_VALUE = "terms";
|
||||
|
||||
private static final URI HTTP_LOCALHOST_URI = URI.create(String.format("%s://%s/", HTTP_SCHEME, LOCALHOST));
|
||||
|
||||
private static final URI HTTP_LOCALHOST_PORT_URI = URI.create(String.format("%s://%s:%d/", HTTP_SCHEME, LOCALHOST, PORT));
|
||||
|
||||
private static final URI HTTP_LOCALHOST_PORT_ENCODED_PATH_URI = URI.create(String.format("%s://%s:%d%s", HTTP_SCHEME, LOCALHOST, PORT, ENCODED_PATH));
|
||||
|
||||
private static final URI HTTP_LOCALHOST_RESOURCES_URI = URI.create(String.format("%s%s", HTTP_LOCALHOST_URI, RESOURCES_PATH_SEGMENT));
|
||||
|
||||
private static final URI HTTP_LOCALHOST_QUERY_URI = URI.create(String.format("%s?%s=%s", HTTP_LOCALHOST_RESOURCES_URI, PARAMETER_NAME, PARAMETER_VALUE));
|
||||
|
||||
private static final URI HTTP_LOCALHOST_QUERY_EMPTY_VALUE_URI = URI.create(String.format("%s?%s", HTTP_LOCALHOST_RESOURCES_URI, PARAMETER_NAME));
|
||||
|
||||
@Test
|
||||
void testBuildIllegalStateException() {
|
||||
final HttpUriBuilder builder = new StandardHttpUriBuilder();
|
||||
|
||||
assertThrows(IllegalStateException.class, builder::build);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuildSchemeHost() {
|
||||
final HttpUriBuilder builder = new StandardHttpUriBuilder()
|
||||
.scheme(HTTP_SCHEME)
|
||||
.host(LOCALHOST);
|
||||
|
||||
final URI uri = builder.build();
|
||||
|
||||
assertEquals(HTTP_LOCALHOST_URI, uri);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuildSchemeHostPort() {
|
||||
final HttpUriBuilder builder = new StandardHttpUriBuilder()
|
||||
.scheme(HTTP_SCHEME)
|
||||
.host(LOCALHOST)
|
||||
.port(PORT);
|
||||
|
||||
final URI uri = builder.build();
|
||||
|
||||
assertEquals(HTTP_LOCALHOST_PORT_URI, uri);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuildSchemeHostPortEncodedPath() {
|
||||
final HttpUriBuilder builder = new StandardHttpUriBuilder()
|
||||
.scheme(HTTP_SCHEME)
|
||||
.host(LOCALHOST)
|
||||
.port(PORT)
|
||||
.encodedPath(ENCODED_PATH);
|
||||
|
||||
final URI uri = builder.build();
|
||||
|
||||
assertEquals(HTTP_LOCALHOST_PORT_ENCODED_PATH_URI, uri);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuildSchemeHostPathSegment() {
|
||||
final HttpUriBuilder builder = new StandardHttpUriBuilder()
|
||||
.scheme(HTTP_SCHEME)
|
||||
.host(LOCALHOST)
|
||||
.addPathSegment(RESOURCES_PATH_SEGMENT);
|
||||
|
||||
final URI uri = builder.build();
|
||||
|
||||
assertEquals(HTTP_LOCALHOST_RESOURCES_URI, uri);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuildSchemeHostPathSegmentQueryParameter() {
|
||||
final HttpUriBuilder builder = new StandardHttpUriBuilder()
|
||||
.scheme(HTTP_SCHEME)
|
||||
.host(LOCALHOST)
|
||||
.addPathSegment(RESOURCES_PATH_SEGMENT)
|
||||
.addQueryParameter(PARAMETER_NAME, PARAMETER_VALUE);
|
||||
|
||||
final URI uri = builder.build();
|
||||
|
||||
assertEquals(HTTP_LOCALHOST_QUERY_URI, uri);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuildSchemeHostPathSegmentQueryParameterNullValue() {
|
||||
final HttpUriBuilder builder = new StandardHttpUriBuilder()
|
||||
.scheme(HTTP_SCHEME)
|
||||
.host(LOCALHOST)
|
||||
.addPathSegment(RESOURCES_PATH_SEGMENT)
|
||||
.addQueryParameter(PARAMETER_NAME, null);
|
||||
|
||||
final URI uri = builder.build();
|
||||
|
||||
assertEquals(HTTP_LOCALHOST_QUERY_EMPTY_VALUE_URI, uri);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,434 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client;
|
||||
|
||||
import okhttp3.Credentials;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.apache.nifi.web.client.api.HttpEntityHeaders;
|
||||
import org.apache.nifi.web.client.api.HttpRequestMethod;
|
||||
import org.apache.nifi.web.client.api.HttpRequestUriSpec;
|
||||
import org.apache.nifi.web.client.api.HttpResponseEntity;
|
||||
import org.apache.nifi.web.client.api.HttpResponseStatus;
|
||||
import org.apache.nifi.web.client.api.StandardHttpRequestMethod;
|
||||
import org.apache.nifi.web.client.api.WebClientServiceException;
|
||||
import org.apache.nifi.web.client.proxy.ProxyContext;
|
||||
import org.apache.nifi.web.client.redirect.RedirectHandling;
|
||||
import org.apache.nifi.web.client.ssl.TlsContext;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Proxy;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class StandardWebClientServiceTest {
|
||||
private static final String ROOT_PATH = "/";
|
||||
|
||||
private static final String LOCALHOST = "localhost";
|
||||
|
||||
private static final byte[] EMPTY_BODY = new byte[0];
|
||||
|
||||
private static final String RESPONSE_BODY = String.class.getSimpleName();
|
||||
|
||||
private static final byte[] TEXT_BODY = RESPONSE_BODY.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
private static final Duration FAILURE_TIMEOUT = Duration.ofMillis(100);
|
||||
|
||||
private static final String ACCEPT_HEADER = "Accept";
|
||||
|
||||
private static final String ACCEPT_ANY_TYPE = "*/*";
|
||||
|
||||
private static final String CONTENT_LENGTH_HEADER = "content-length";
|
||||
|
||||
private static final String CONTENT_LENGTH_ZERO = "0";
|
||||
|
||||
private static final String LOCATION_HEADER = "Location";
|
||||
|
||||
private static final String PROXY_AUTHENTICATE_HEADER = "Proxy-Authenticate";
|
||||
|
||||
private static final String PROXY_AUTHENTICATE_BASIC_REALM = "Basic realm=\"Authentication Required\"";
|
||||
|
||||
private static final String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
|
||||
|
||||
private static final String TLS_PROTOCOL = "TLS";
|
||||
|
||||
private static final String TLS_PROTOCOL_UNSUPPORTED = "TLSv0";
|
||||
|
||||
private static final X509Certificate[] TRUSTED_ISSUERS = new X509Certificate[0];
|
||||
|
||||
@Mock
|
||||
TlsContext tlsContext;
|
||||
|
||||
@Mock
|
||||
ProxyContext proxyContext;
|
||||
|
||||
@Mock
|
||||
X509TrustManager trustManager;
|
||||
|
||||
MockWebServer mockWebServer;
|
||||
|
||||
StandardWebClientService service;
|
||||
|
||||
@BeforeEach
|
||||
void setServer() {
|
||||
mockWebServer = new MockWebServer();
|
||||
service = new StandardWebClientService();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void shutdownServer() throws IOException {
|
||||
mockWebServer.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetTlsContext() {
|
||||
when(tlsContext.getProtocol()).thenReturn(TLS_PROTOCOL);
|
||||
when(tlsContext.getTrustManager()).thenReturn(trustManager);
|
||||
when(trustManager.getAcceptedIssuers()).thenReturn(TRUSTED_ISSUERS);
|
||||
|
||||
service.setTlsContext(tlsContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetTlsContextProtocolNotSupported() {
|
||||
when(tlsContext.getProtocol()).thenReturn(TLS_PROTOCOL_UNSUPPORTED);
|
||||
when(tlsContext.getTrustManager()).thenReturn(trustManager);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> service.setTlsContext(tlsContext));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSocketTimeoutException() throws IOException {
|
||||
mockWebServer.shutdown();
|
||||
|
||||
service.setConnectTimeout(FAILURE_TIMEOUT);
|
||||
service.setReadTimeout(FAILURE_TIMEOUT);
|
||||
service.setWriteTimeout(FAILURE_TIMEOUT);
|
||||
|
||||
when(proxyContext.getProxy()).thenReturn(Proxy.NO_PROXY);
|
||||
service.setProxyContext(proxyContext);
|
||||
|
||||
final WebClientServiceException exception = assertThrows(WebClientServiceException.class, () ->
|
||||
service.method(StandardHttpRequestMethod.GET)
|
||||
.uri(getRootUri())
|
||||
.retrieve()
|
||||
);
|
||||
|
||||
assertInstanceOf(SocketTimeoutException.class, exception.getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProxyAuthorization() throws IOException, InterruptedException {
|
||||
final Proxy proxy = mockWebServer.toProxyAddress();
|
||||
when(proxyContext.getProxy()).thenReturn(proxy);
|
||||
final String username = String.class.getSimpleName();
|
||||
final String password = String.class.getName();
|
||||
when(proxyContext.getUsername()).thenReturn(Optional.of(username));
|
||||
when(proxyContext.getPassword()).thenReturn(Optional.of(password));
|
||||
service.setProxyContext(proxyContext);
|
||||
|
||||
mockWebServer.enqueue(new MockResponse()
|
||||
.setResponseCode(HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED.getCode())
|
||||
.setHeader(PROXY_AUTHENTICATE_HEADER, PROXY_AUTHENTICATE_BASIC_REALM)
|
||||
);
|
||||
|
||||
runRequestMethod(service.get(), StandardHttpRequestMethod.GET, HttpResponseStatus.OK);
|
||||
|
||||
final RecordedRequest proxyAuthorizationRequest = mockWebServer.takeRequest();
|
||||
final String proxyAuthorization = proxyAuthorizationRequest.getHeader(PROXY_AUTHORIZATION_HEADER);
|
||||
final String credentials = Credentials.basic(username, password);
|
||||
assertEquals(credentials, proxyAuthorization);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRedirectHandlingFollowed() throws InterruptedException {
|
||||
service.setRedirectHandling(RedirectHandling.FOLLOWED);
|
||||
|
||||
final String location = mockWebServer.url(ROOT_PATH).newBuilder().host(LOCALHOST).build().toString();
|
||||
|
||||
final MockResponse movedResponse = new MockResponse()
|
||||
.setResponseCode(HttpResponseStatus.MOVED_PERMANENTLY.getCode())
|
||||
.setHeader(LOCATION_HEADER, location);
|
||||
mockWebServer.enqueue(movedResponse);
|
||||
|
||||
final HttpResponseStatus httpResponseStatus = HttpResponseStatus.OK;
|
||||
enqueueResponseStatus(httpResponseStatus);
|
||||
|
||||
final HttpResponseEntity httpResponseEntity = service.get()
|
||||
.uri(getRootUri())
|
||||
.retrieve();
|
||||
|
||||
assertRecordedRequestResponseStatus(httpResponseEntity, StandardHttpRequestMethod.GET, httpResponseStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRedirectHandlingIgnored() throws InterruptedException {
|
||||
service.setRedirectHandling(RedirectHandling.IGNORED);
|
||||
|
||||
final HttpResponseStatus httpResponseStatus = HttpResponseStatus.MOVED_PERMANENTLY;
|
||||
|
||||
enqueueResponseStatusBody(httpResponseStatus);
|
||||
|
||||
final HttpResponseEntity httpResponseEntity = service.get()
|
||||
.uri(getRootUri())
|
||||
.retrieve();
|
||||
|
||||
assertRecordedRequestResponseStatus(httpResponseEntity, StandardHttpRequestMethod.GET, httpResponseStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDelete() throws InterruptedException, IOException {
|
||||
runRequestMethod(service.delete(), StandardHttpRequestMethod.DELETE, HttpResponseStatus.NO_CONTENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeleteMethodNotAllowed() throws InterruptedException, IOException {
|
||||
runRequestMethod(service.delete(), StandardHttpRequestMethod.DELETE, HttpResponseStatus.METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGet() throws InterruptedException, IOException {
|
||||
runRequestMethod(service.get(), StandardHttpRequestMethod.GET, HttpResponseStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetNotFound() throws InterruptedException, IOException {
|
||||
runRequestMethod(service.get(), StandardHttpRequestMethod.GET, HttpResponseStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetInternalServerError() throws InterruptedException, IOException {
|
||||
runRequestMethod(service.get(), StandardHttpRequestMethod.GET, HttpResponseStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetServiceUnavailable() throws InterruptedException, IOException {
|
||||
runRequestMethod(service.get(), StandardHttpRequestMethod.GET, HttpResponseStatus.SERVICE_UNAVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPatch() throws InterruptedException, IOException {
|
||||
runRequestMethodRequestBody(service.patch(), StandardHttpRequestMethod.PATCH, HttpResponseStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPatchBadRequest() throws InterruptedException, IOException {
|
||||
runRequestMethodRequestBody(service.patch(), StandardHttpRequestMethod.PATCH, HttpResponseStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPost() throws InterruptedException, IOException {
|
||||
runRequestMethodRequestBody(service.post(), StandardHttpRequestMethod.POST, HttpResponseStatus.CREATED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPostUnauthorized() throws InterruptedException, IOException {
|
||||
runRequestMethodRequestBody(service.post(), StandardHttpRequestMethod.POST, HttpResponseStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPut() throws InterruptedException, IOException {
|
||||
runRequestMethodRequestBody(service.put(), StandardHttpRequestMethod.PUT, HttpResponseStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPutForbidden() throws InterruptedException, IOException {
|
||||
runRequestMethodRequestBody(service.put(), StandardHttpRequestMethod.PUT, HttpResponseStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(value = StandardHttpRequestMethod.class, names = {"DELETE", "GET"})
|
||||
void testHttpRequestMethod(final StandardHttpRequestMethod httpRequestMethod) throws InterruptedException, IOException {
|
||||
runRequestMethod(service.method(httpRequestMethod), httpRequestMethod, HttpResponseStatus.NO_CONTENT);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(value = StandardHttpRequestMethod.class, names = {"DELETE", "GET"})
|
||||
void testHttpRequestMethodResponseBody(final StandardHttpRequestMethod httpRequestMethod) throws InterruptedException, IOException {
|
||||
final HttpResponseStatus httpResponseStatus = HttpResponseStatus.ACCEPTED;
|
||||
enqueueResponseStatusBody(httpResponseStatus);
|
||||
|
||||
final HttpResponseEntity httpResponseEntity = service.method(httpRequestMethod)
|
||||
.uri(getRootUri())
|
||||
.retrieve();
|
||||
|
||||
assertRecordedRequestResponseStatus(httpResponseEntity, httpRequestMethod, httpResponseStatus);
|
||||
|
||||
try (final InputStream body = httpResponseEntity.body()) {
|
||||
final byte[] responseBody = new byte[TEXT_BODY.length];
|
||||
final int bytesRead = body.read(responseBody);
|
||||
assertEquals(TEXT_BODY.length, bytesRead);
|
||||
assertArrayEquals(TEXT_BODY, responseBody);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(value = StandardHttpRequestMethod.class, names = {"PATCH", "POST", "PUT"})
|
||||
void testHttpRequestMethodRequestBodyEmpty(final StandardHttpRequestMethod httpRequestMethod) throws InterruptedException, IOException {
|
||||
final HttpResponseStatus httpResponseStatus = HttpResponseStatus.ACCEPTED;
|
||||
enqueueResponseStatus(httpResponseStatus);
|
||||
|
||||
final InputStream body = new ByteArrayInputStream(EMPTY_BODY);
|
||||
|
||||
try (final HttpResponseEntity httpResponseEntity = service.method(httpRequestMethod)
|
||||
.uri(getRootUri())
|
||||
.body(body, OptionalLong.empty())
|
||||
.retrieve()
|
||||
) {
|
||||
assertRecordedRequestResponseStatus(httpResponseEntity, httpRequestMethod, httpResponseStatus);
|
||||
assertContentLengthHeaderFound(httpResponseEntity.headers());
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(value = StandardHttpRequestMethod.class, names = {"PATCH", "POST", "PUT"})
|
||||
void testHttpRequestMethodRequestBody(final StandardHttpRequestMethod httpRequestMethod) throws InterruptedException, IOException {
|
||||
final HttpResponseStatus httpResponseStatus = HttpResponseStatus.ACCEPTED;
|
||||
enqueueResponseStatus(httpResponseStatus);
|
||||
|
||||
final InputStream body = new ByteArrayInputStream(TEXT_BODY);
|
||||
|
||||
try (final HttpResponseEntity httpResponseEntity = service.method(httpRequestMethod)
|
||||
.uri(getRootUri())
|
||||
.header(ACCEPT_HEADER, ACCEPT_ANY_TYPE)
|
||||
.body(body, OptionalLong.of(TEXT_BODY.length))
|
||||
.retrieve()
|
||||
) {
|
||||
|
||||
final HttpEntityHeaders headers = httpResponseEntity.headers();
|
||||
assertContentLengthHeaderFound(headers);
|
||||
|
||||
final RecordedRequest recordedRequest = assertRecordedRequestResponseStatus(httpResponseEntity, httpRequestMethod, httpResponseStatus);
|
||||
|
||||
assertEquals(TEXT_BODY.length, recordedRequest.getBodySize());
|
||||
final byte[] requestBody = recordedRequest.getBody().readByteArray();
|
||||
assertArrayEquals(TEXT_BODY, requestBody);
|
||||
|
||||
final String acceptHeader = recordedRequest.getHeader(ACCEPT_HEADER);
|
||||
assertEquals(ACCEPT_ANY_TYPE, acceptHeader);
|
||||
}
|
||||
}
|
||||
|
||||
private void runRequestMethod(
|
||||
final HttpRequestUriSpec httpRequestUriSpec,
|
||||
final HttpRequestMethod httpRequestMethod,
|
||||
final HttpResponseStatus httpResponseStatus
|
||||
) throws IOException, InterruptedException {
|
||||
enqueueResponseStatus(httpResponseStatus);
|
||||
|
||||
try (final HttpResponseEntity httpResponseEntity = httpRequestUriSpec
|
||||
.uri(getRootUri())
|
||||
.retrieve()
|
||||
) {
|
||||
assertRecordedRequestResponseStatus(httpResponseEntity, httpRequestMethod, httpResponseStatus);
|
||||
assertContentLengthHeaderFound(httpResponseEntity.headers());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void runRequestMethodRequestBody(
|
||||
final HttpRequestUriSpec httpRequestUriSpec,
|
||||
final HttpRequestMethod httpRequestMethod,
|
||||
final HttpResponseStatus httpResponseStatus
|
||||
) throws IOException, InterruptedException {
|
||||
enqueueResponseStatus(httpResponseStatus);
|
||||
|
||||
final InputStream body = new ByteArrayInputStream(EMPTY_BODY);
|
||||
|
||||
try (final HttpResponseEntity httpResponseEntity = httpRequestUriSpec
|
||||
.uri(getRootUri())
|
||||
.body(body, OptionalLong.empty())
|
||||
.retrieve()
|
||||
) {
|
||||
assertRecordedRequestResponseStatus(httpResponseEntity, httpRequestMethod, httpResponseStatus);
|
||||
assertContentLengthHeaderFound(httpResponseEntity.headers());
|
||||
}
|
||||
}
|
||||
|
||||
private RecordedRequest assertRecordedRequestResponseStatus(
|
||||
final HttpResponseEntity httpResponseEntity,
|
||||
final HttpRequestMethod httpRequestMethod,
|
||||
final HttpResponseStatus httpResponseStatus
|
||||
) throws InterruptedException {
|
||||
assertNotNull(httpResponseEntity);
|
||||
|
||||
final RecordedRequest recordedRequest = mockWebServer.takeRequest();
|
||||
assertEquals(httpRequestMethod.getMethod(), recordedRequest.getMethod());
|
||||
|
||||
assertEquals(httpResponseStatus.getCode(), httpResponseEntity.statusCode());
|
||||
|
||||
return recordedRequest;
|
||||
}
|
||||
|
||||
private void assertContentLengthHeaderFound(final HttpEntityHeaders headers) {
|
||||
final Optional<String> contentLengthHeader = headers.getFirstHeader(CONTENT_LENGTH_HEADER);
|
||||
assertTrue(contentLengthHeader.isPresent());
|
||||
assertEquals(CONTENT_LENGTH_ZERO, contentLengthHeader.get());
|
||||
|
||||
final List<String> contentLengthHeaders = headers.getHeader(CONTENT_LENGTH_HEADER);
|
||||
assertFalse(contentLengthHeaders.isEmpty());
|
||||
assertEquals(Collections.singletonList(CONTENT_LENGTH_ZERO), contentLengthHeaders);
|
||||
|
||||
final Collection<String> headerNames = headers.getHeaderNames();
|
||||
assertTrue(headerNames.contains(CONTENT_LENGTH_HEADER));
|
||||
}
|
||||
|
||||
private void enqueueResponseStatus(final HttpResponseStatus httpResponseStatus) {
|
||||
mockWebServer.enqueue(new MockResponse().setResponseCode(httpResponseStatus.getCode()));
|
||||
}
|
||||
|
||||
private void enqueueResponseStatusBody(final HttpResponseStatus httpResponseStatus) {
|
||||
mockWebServer.enqueue(new MockResponse()
|
||||
.setResponseCode(httpResponseStatus.getCode())
|
||||
.setBody(RESPONSE_BODY)
|
||||
);
|
||||
}
|
||||
|
||||
private URI getRootUri() {
|
||||
return mockWebServer.url(ROOT_PATH).newBuilder().host(LOCALHOST).build().uri();
|
||||
}
|
||||
}
|
|
@ -64,6 +64,8 @@
|
|||
<module>nifi-utils</module>
|
||||
<module>nifi-uuid5</module>
|
||||
<module>nifi-vault-utils</module>
|
||||
<module>nifi-web-client</module>
|
||||
<module>nifi-web-client-api</module>
|
||||
<module>nifi-web-utils</module>
|
||||
<module>nifi-write-ahead-log</module>
|
||||
<module>nifi-xml-processing</module>
|
||||
|
|
|
@ -127,5 +127,11 @@
|
|||
<version>1.18.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-client-provider-api</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF 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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-client-provider-bundle</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-web-client-provider-api</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-client-api</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.provider.api;
|
||||
|
||||
import org.apache.nifi.controller.ControllerService;
|
||||
import org.apache.nifi.web.client.api.HttpUriBuilder;
|
||||
import org.apache.nifi.web.client.api.WebClientService;
|
||||
|
||||
/**
|
||||
* Web Client Service Provider abstracts configuration of Web Client Service instances
|
||||
*/
|
||||
public interface WebClientServiceProvider extends ControllerService {
|
||||
/**
|
||||
* Get new HTTP URI Builder
|
||||
*
|
||||
* @return New instance of HTTP URI Builder
|
||||
*/
|
||||
HttpUriBuilder getHttpUriBuilder();
|
||||
|
||||
/**
|
||||
* Get Web Client Service based on current configuration
|
||||
*
|
||||
* @return Configured Web Client Service
|
||||
*/
|
||||
WebClientService getWebClientService();
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF 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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-client-provider-bundle</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-web-client-provider-service-nar</artifactId>
|
||||
<packaging>nar</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-client-provider-service</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-standard-services-api-nar</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
<type>nar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,72 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF 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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-client-provider-bundle</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-web-client-provider-service</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-client-provider-api</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-ssl-context-service-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-proxy-configuration-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-client-api</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-client</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-utils</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-mock</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.provider.service;
|
||||
|
||||
import org.apache.nifi.ssl.SSLContextService;
|
||||
|
||||
import javax.net.ssl.X509KeyManager;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Provider abstraction for loading a Key Manager
|
||||
*/
|
||||
interface KeyManagerProvider {
|
||||
/**
|
||||
* Get X.509 Key Manager
|
||||
*
|
||||
* @param sslContextService SSL Context Service
|
||||
* @return X.509 Key Manager or empty when not configured
|
||||
*/
|
||||
Optional<X509KeyManager> getKeyManager(SSLContextService sslContextService);
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.provider.service;
|
||||
|
||||
import org.apache.nifi.ssl.SSLContextService;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.X509KeyManager;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Standard implementation of Key Manager Provider
|
||||
*/
|
||||
class StandardKeyManagerProvider implements KeyManagerProvider {
|
||||
/**
|
||||
* Get X.509 Key Manager using SSL Context Service configuration properties
|
||||
*
|
||||
* @param sslContextService SSL Context Service
|
||||
* @return X.509 Key Manager or empty when not configured
|
||||
*/
|
||||
@Override
|
||||
public Optional<X509KeyManager> getKeyManager(final SSLContextService sslContextService) {
|
||||
final X509KeyManager keyManager;
|
||||
|
||||
if (sslContextService.isKeyStoreConfigured()) {
|
||||
final KeyManagerFactory keyManagerFactory = getKeyManagerFactory();
|
||||
final KeyStore keyStore = getKeyStore(sslContextService);
|
||||
final char[] keyPassword = getKeyPassword(sslContextService);
|
||||
try {
|
||||
keyManagerFactory.init(keyStore, keyPassword);
|
||||
} catch (final KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
|
||||
throw new IllegalStateException("Key Manager Factory initialization failed", e);
|
||||
}
|
||||
|
||||
final KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
|
||||
final Optional<KeyManager> firstKeyManager = Arrays.stream(keyManagers).findFirst();
|
||||
final KeyManager configuredKeyManager = firstKeyManager.orElse(null);
|
||||
keyManager = configuredKeyManager instanceof X509KeyManager ? (X509KeyManager) configuredKeyManager : null;
|
||||
} else {
|
||||
keyManager = null;
|
||||
}
|
||||
|
||||
return Optional.ofNullable(keyManager);
|
||||
}
|
||||
|
||||
private KeyStore getKeyStore(final SSLContextService sslContextService) {
|
||||
final String keyStoreType = sslContextService.getKeyStoreType();
|
||||
final KeyStore keyStore = getKeyStore(keyStoreType);
|
||||
final char[] keyStorePassword = sslContextService.getKeyStorePassword().toCharArray();
|
||||
final String keyStoreFile = sslContextService.getKeyStoreFile();
|
||||
try {
|
||||
try (final InputStream inputStream = new FileInputStream(keyStoreFile)) {
|
||||
keyStore.load(inputStream, keyStorePassword);
|
||||
}
|
||||
return keyStore;
|
||||
} catch (final IOException e) {
|
||||
throw new IllegalStateException(String.format("Key Store File [%s] reading failed", keyStoreFile), e);
|
||||
} catch (final NoSuchAlgorithmException | CertificateException e) {
|
||||
throw new IllegalStateException(String.format("Key Store File [%s] loading failed", keyStoreFile), e);
|
||||
}
|
||||
}
|
||||
|
||||
private KeyStore getKeyStore(final String keyStoreType) {
|
||||
try {
|
||||
return KeyStore.getInstance(keyStoreType);
|
||||
} catch (final KeyStoreException e) {
|
||||
throw new IllegalStateException(String.format("Key Store Type [%s] creation failed", keyStoreType), e);
|
||||
}
|
||||
}
|
||||
|
||||
private char[] getKeyPassword(final SSLContextService sslContextService) {
|
||||
final String keyPassword = sslContextService.getKeyPassword();
|
||||
final String keyStorePassword = sslContextService.getKeyStorePassword();
|
||||
final String password = keyPassword == null ? keyStorePassword : keyPassword;
|
||||
return password.toCharArray();
|
||||
}
|
||||
|
||||
private KeyManagerFactory getKeyManagerFactory() {
|
||||
try {
|
||||
return KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException("Key Manager Factory creation failed", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.provider.service;
|
||||
|
||||
import org.apache.nifi.annotation.documentation.CapabilityDescription;
|
||||
import org.apache.nifi.annotation.documentation.Tags;
|
||||
import org.apache.nifi.annotation.lifecycle.OnEnabled;
|
||||
import org.apache.nifi.components.PropertyDescriptor;
|
||||
import org.apache.nifi.components.PropertyValue;
|
||||
import org.apache.nifi.controller.AbstractControllerService;
|
||||
import org.apache.nifi.controller.ConfigurationContext;
|
||||
import org.apache.nifi.processor.util.StandardValidators;
|
||||
import org.apache.nifi.proxy.ProxyConfiguration;
|
||||
import org.apache.nifi.proxy.ProxyConfigurationService;
|
||||
import org.apache.nifi.ssl.SSLContextService;
|
||||
import org.apache.nifi.web.client.StandardHttpUriBuilder;
|
||||
import org.apache.nifi.web.client.api.HttpUriBuilder;
|
||||
import org.apache.nifi.web.client.proxy.ProxyContext;
|
||||
import org.apache.nifi.web.client.StandardWebClientService;
|
||||
import org.apache.nifi.web.client.redirect.RedirectHandling;
|
||||
import org.apache.nifi.web.client.ssl.TlsContext;
|
||||
import org.apache.nifi.web.client.api.WebClientService;
|
||||
import org.apache.nifi.web.client.provider.api.WebClientServiceProvider;
|
||||
|
||||
import javax.net.ssl.X509KeyManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.net.Proxy;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.apache.nifi.proxy.ProxyConfigurationService.PROXY_CONFIGURATION_SERVICE;
|
||||
|
||||
@CapabilityDescription("Web Client Service Provider with support for configuring standard HTTP connection properties")
|
||||
@Tags({ "HTTP", "Web", "Client" })
|
||||
public class StandardWebClientServiceProvider extends AbstractControllerService implements WebClientServiceProvider {
|
||||
|
||||
static final PropertyDescriptor CONNECT_TIMEOUT = new PropertyDescriptor.Builder()
|
||||
.name("connect-timeout")
|
||||
.displayName("Connect Timeout")
|
||||
.description("Maximum amount of time to wait before failing during initial socket connection")
|
||||
.required(true)
|
||||
.defaultValue("10 secs")
|
||||
.addValidator(StandardValidators.TIME_PERIOD_VALIDATOR)
|
||||
.build();
|
||||
|
||||
static final PropertyDescriptor READ_TIMEOUT = new PropertyDescriptor.Builder()
|
||||
.name("read-timeout")
|
||||
.displayName("Read Timeout")
|
||||
.description("Maximum amount of time to wait before failing while reading socket responses")
|
||||
.required(true)
|
||||
.defaultValue("10 secs")
|
||||
.addValidator(StandardValidators.TIME_PERIOD_VALIDATOR)
|
||||
.build();
|
||||
|
||||
static final PropertyDescriptor WRITE_TIMEOUT = new PropertyDescriptor.Builder()
|
||||
.name("write-timeout")
|
||||
.displayName("Write Timeout")
|
||||
.description("Maximum amount of time to wait before failing while writing socket requests")
|
||||
.required(true)
|
||||
.defaultValue("10 secs")
|
||||
.addValidator(StandardValidators.TIME_PERIOD_VALIDATOR)
|
||||
.build();
|
||||
|
||||
static final PropertyDescriptor REDIRECT_HANDLING_STRATEGY = new PropertyDescriptor.Builder()
|
||||
.name("redirect-handling-strategy")
|
||||
.displayName("Redirect Handling Strategy")
|
||||
.description("Handling strategy for responding to HTTP 301 or 302 redirects received with a Location header")
|
||||
.required(true)
|
||||
.defaultValue(RedirectHandling.FOLLOWED.name())
|
||||
.allowableValues(RedirectHandling.values())
|
||||
.build();
|
||||
|
||||
static final PropertyDescriptor SSL_CONTEXT_SERVICE = new PropertyDescriptor.Builder()
|
||||
.name("ssl-context-service")
|
||||
.displayName("SSL Context Service")
|
||||
.description("SSL Context Service overrides system default TLS settings for HTTPS communication")
|
||||
.required(false)
|
||||
.identifiesControllerService(SSLContextService.class)
|
||||
.build();
|
||||
|
||||
static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = Arrays.asList(
|
||||
CONNECT_TIMEOUT,
|
||||
READ_TIMEOUT,
|
||||
WRITE_TIMEOUT,
|
||||
REDIRECT_HANDLING_STRATEGY,
|
||||
SSL_CONTEXT_SERVICE,
|
||||
PROXY_CONFIGURATION_SERVICE
|
||||
);
|
||||
|
||||
private static final KeyManagerProvider keyManagerProvider = new StandardKeyManagerProvider();
|
||||
|
||||
private WebClientService webClientService;
|
||||
|
||||
@OnEnabled
|
||||
public void onEnabled(final ConfigurationContext context) {
|
||||
final StandardWebClientService standardWebClientService = new StandardWebClientService();
|
||||
|
||||
final Duration connectTimeout = getDuration(context, CONNECT_TIMEOUT);
|
||||
standardWebClientService.setConnectTimeout(connectTimeout);
|
||||
|
||||
final Duration readTimeout = getDuration(context, READ_TIMEOUT);
|
||||
standardWebClientService.setReadTimeout(readTimeout);
|
||||
|
||||
final Duration writeTimeout = getDuration(context, WRITE_TIMEOUT);
|
||||
standardWebClientService.setReadTimeout(writeTimeout);
|
||||
|
||||
final String redirectHandlingStrategy = context.getProperty(REDIRECT_HANDLING_STRATEGY).getValue();
|
||||
final RedirectHandling redirectHandling = RedirectHandling.valueOf(redirectHandlingStrategy);
|
||||
standardWebClientService.setRedirectHandling(redirectHandling);
|
||||
|
||||
final PropertyValue sslContextServiceProperty = context.getProperty(SSL_CONTEXT_SERVICE);
|
||||
if (sslContextServiceProperty.isSet()) {
|
||||
final SSLContextService sslContextService = sslContextServiceProperty.asControllerService(SSLContextService.class);
|
||||
final TlsContext tlsContext = getTlsContext(sslContextService);
|
||||
standardWebClientService.setTlsContext(tlsContext);
|
||||
}
|
||||
|
||||
final PropertyValue proxyConfigurationServiceProperty = context.getProperty(PROXY_CONFIGURATION_SERVICE);
|
||||
if (proxyConfigurationServiceProperty.isSet()) {
|
||||
final ProxyConfigurationService proxyConfigurationService = context.getProperty(PROXY_CONFIGURATION_SERVICE).asControllerService(ProxyConfigurationService.class);
|
||||
final ProxyConfiguration proxyConfiguration = proxyConfigurationService.getConfiguration();
|
||||
final ProxyContext proxyContext = getProxyContext(proxyConfiguration);
|
||||
standardWebClientService.setProxyContext(proxyContext);
|
||||
}
|
||||
|
||||
webClientService = standardWebClientService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpUriBuilder getHttpUriBuilder() {
|
||||
return new StandardHttpUriBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebClientService getWebClientService() {
|
||||
return webClientService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
|
||||
return PROPERTY_DESCRIPTORS;
|
||||
}
|
||||
|
||||
private Duration getDuration(final ConfigurationContext context, final PropertyDescriptor propertyDescriptor) {
|
||||
final long millis = context.getProperty(propertyDescriptor).asTimePeriod(TimeUnit.MILLISECONDS);
|
||||
return Duration.ofMillis(millis);
|
||||
}
|
||||
|
||||
private TlsContext getTlsContext(final SSLContextService sslContextService) {
|
||||
final X509TrustManager trustManager = sslContextService.createTrustManager();
|
||||
final Optional<X509KeyManager> keyManager = keyManagerProvider.getKeyManager(sslContextService);
|
||||
|
||||
return new TlsContext() {
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return sslContextService.getSslAlgorithm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509TrustManager getTrustManager() {
|
||||
return trustManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<X509KeyManager> getKeyManager() {
|
||||
return keyManager;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ProxyContext getProxyContext(final ProxyConfiguration proxyConfiguration) {
|
||||
return new ProxyContext() {
|
||||
@Override
|
||||
public Proxy getProxy() {
|
||||
return proxyConfiguration.createProxy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getUsername() {
|
||||
return Optional.ofNullable(proxyConfiguration.getProxyUserName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getPassword() {
|
||||
return Optional.ofNullable(proxyConfiguration.getProxyUserPassword());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF 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.
|
||||
org.apache.nifi.web.client.provider.service.StandardWebClientServiceProvider
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.provider.service;
|
||||
|
||||
import org.apache.nifi.security.util.TemporaryKeyStoreBuilder;
|
||||
import org.apache.nifi.security.util.TlsConfiguration;
|
||||
import org.apache.nifi.ssl.SSLContextService;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import javax.net.ssl.X509KeyManager;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class StandardKeyManagerProviderTest {
|
||||
static TlsConfiguration tlsConfiguration;
|
||||
|
||||
@Mock
|
||||
SSLContextService sslContextService;
|
||||
|
||||
StandardKeyManagerProvider provider;
|
||||
|
||||
@BeforeAll
|
||||
static void setTlsConfiguration() {
|
||||
tlsConfiguration = new TemporaryKeyStoreBuilder().build();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setProvider() {
|
||||
provider = new StandardKeyManagerProvider();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetKeyManagerNotConfigured() {
|
||||
when(sslContextService.isKeyStoreConfigured()).thenReturn(false);
|
||||
|
||||
final Optional<X509KeyManager> keyManager = provider.getKeyManager(sslContextService);
|
||||
|
||||
assertFalse(keyManager.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetKeyManager() {
|
||||
when(sslContextService.isKeyStoreConfigured()).thenReturn(true);
|
||||
when(sslContextService.getKeyStoreType()).thenReturn(tlsConfiguration.getKeystoreType().getType());
|
||||
when(sslContextService.getKeyStoreFile()).thenReturn(tlsConfiguration.getKeystorePath());
|
||||
when(sslContextService.getKeyStorePassword()).thenReturn(tlsConfiguration.getKeystorePassword());
|
||||
|
||||
final Optional<X509KeyManager> keyManager = provider.getKeyManager(sslContextService);
|
||||
|
||||
assertTrue(keyManager.isPresent());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.nifi.web.client.provider.service;
|
||||
|
||||
import okhttp3.Credentials;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.apache.nifi.proxy.ProxyConfiguration;
|
||||
import org.apache.nifi.proxy.ProxyConfigurationService;
|
||||
import org.apache.nifi.reporting.InitializationException;
|
||||
import org.apache.nifi.security.util.SslContextFactory;
|
||||
import org.apache.nifi.security.util.TemporaryKeyStoreBuilder;
|
||||
import org.apache.nifi.security.util.TlsConfiguration;
|
||||
import org.apache.nifi.security.util.TlsException;
|
||||
import org.apache.nifi.ssl.SSLContextService;
|
||||
import org.apache.nifi.util.NoOpProcessor;
|
||||
import org.apache.nifi.util.TestRunner;
|
||||
import org.apache.nifi.util.TestRunners;
|
||||
import org.apache.nifi.web.client.api.HttpResponseEntity;
|
||||
import org.apache.nifi.web.client.api.HttpResponseStatus;
|
||||
import org.apache.nifi.web.client.api.HttpUriBuilder;
|
||||
import org.apache.nifi.web.client.api.WebClientService;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class StandardWebClientServiceProviderTest {
|
||||
private static final String SERVICE_ID = StandardWebClientServiceProvider.class.getSimpleName();
|
||||
|
||||
private static final String SSL_CONTEXT_SERVICE_ID = SSLContextService.class.getSimpleName();
|
||||
|
||||
private static final String PROXY_SERVICE_ID = ProxyConfigurationService.class.getSimpleName();
|
||||
|
||||
private static final String LOCALHOST = "localhost";
|
||||
|
||||
private static final String HTTPS = "https";
|
||||
|
||||
private static final int PORT = 8443;
|
||||
|
||||
private static final String PATH_SEGMENT = "resources";
|
||||
|
||||
private static final String PARAMETER_NAME = "search";
|
||||
|
||||
private static final String PARAMETER_VALUE = "search";
|
||||
|
||||
private static final String ROOT_PATH = "/";
|
||||
|
||||
private static final URI LOCALHOST_URI = URI.create(String.format("%s://%s:%d/%s?%s=%s", HTTPS, LOCALHOST, PORT, PATH_SEGMENT, PARAMETER_NAME, PARAMETER_VALUE));
|
||||
|
||||
private static final String PROXY_AUTHENTICATE_HEADER = "Proxy-Authenticate";
|
||||
|
||||
private static final String PROXY_AUTHENTICATE_BASIC_REALM = "Basic realm=\"Authentication Required\"";
|
||||
|
||||
private static final String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
|
||||
|
||||
private static final boolean TUNNEL_PROXY_DISABLED = false;
|
||||
|
||||
static TlsConfiguration tlsConfiguration;
|
||||
|
||||
static SSLContext sslContext;
|
||||
|
||||
static X509TrustManager trustManager;
|
||||
|
||||
@Mock
|
||||
SSLContextService sslContextService;
|
||||
|
||||
@Mock
|
||||
ProxyConfigurationService proxyConfigurationService;
|
||||
|
||||
TestRunner runner;
|
||||
|
||||
MockWebServer mockWebServer;
|
||||
|
||||
StandardWebClientServiceProvider provider;
|
||||
|
||||
@BeforeAll
|
||||
static void setTlsConfiguration() throws TlsException {
|
||||
tlsConfiguration = new TemporaryKeyStoreBuilder().build();
|
||||
sslContext = SslContextFactory.createSslContext(tlsConfiguration);
|
||||
trustManager = SslContextFactory.getX509TrustManager(tlsConfiguration);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setRunner() throws InitializationException {
|
||||
mockWebServer = new MockWebServer();
|
||||
|
||||
runner = TestRunners.newTestRunner(NoOpProcessor.class);
|
||||
|
||||
provider = new StandardWebClientServiceProvider();
|
||||
runner.addControllerService(SERVICE_ID, provider);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void shutdownServer() throws IOException {
|
||||
mockWebServer.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEnable() {
|
||||
runner.enableControllerService(provider);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetHttpUriBuilder() {
|
||||
runner.enableControllerService(provider);
|
||||
|
||||
final HttpUriBuilder httpUriBuilder = provider.getHttpUriBuilder();
|
||||
|
||||
final URI uri = httpUriBuilder.scheme(HTTPS)
|
||||
.host(LOCALHOST)
|
||||
.port(PORT)
|
||||
.addPathSegment(PATH_SEGMENT)
|
||||
.addQueryParameter(PARAMETER_NAME, PARAMETER_VALUE)
|
||||
.build();
|
||||
|
||||
assertEquals(LOCALHOST_URI, uri);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetWebServiceClientGetUri() throws InterruptedException {
|
||||
runner.enableControllerService(provider);
|
||||
|
||||
final WebClientService webClientService = provider.getWebClientService();
|
||||
|
||||
assertNotNull(webClientService);
|
||||
|
||||
assertGetUriCompleted(webClientService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetWebServiceClientSslContextServiceConfiguredGetUri() throws InitializationException, InterruptedException {
|
||||
when(sslContextService.getIdentifier()).thenReturn(SSL_CONTEXT_SERVICE_ID);
|
||||
when(sslContextService.getSslAlgorithm()).thenReturn(tlsConfiguration.getProtocol());
|
||||
when(sslContextService.createTrustManager()).thenReturn(trustManager);
|
||||
|
||||
runner.addControllerService(SSL_CONTEXT_SERVICE_ID, sslContextService);
|
||||
runner.enableControllerService(sslContextService);
|
||||
|
||||
runner.setProperty(provider, StandardWebClientServiceProvider.SSL_CONTEXT_SERVICE, SSL_CONTEXT_SERVICE_ID);
|
||||
runner.enableControllerService(provider);
|
||||
|
||||
final WebClientService webClientService = provider.getWebClientService();
|
||||
|
||||
assertNotNull(webClientService);
|
||||
|
||||
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
|
||||
mockWebServer.useHttps(sslSocketFactory, TUNNEL_PROXY_DISABLED);
|
||||
|
||||
assertGetUriCompleted(webClientService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetWebServiceClientProxyConfigurationGetUri() throws InitializationException, InterruptedException {
|
||||
final Proxy proxy = mockWebServer.toProxyAddress();
|
||||
final InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
|
||||
|
||||
final ProxyConfiguration proxyConfiguration = new ProxyConfiguration();
|
||||
proxyConfiguration.setProxyType(Proxy.Type.HTTP);
|
||||
proxyConfiguration.setProxyServerHost(proxyAddress.getHostName());
|
||||
proxyConfiguration.setProxyServerPort(proxyAddress.getPort());
|
||||
|
||||
final String username = String.class.getSimpleName();
|
||||
final String password = String.class.getName();
|
||||
proxyConfiguration.setProxyUserName(username);
|
||||
proxyConfiguration.setProxyUserPassword(password);
|
||||
|
||||
when(proxyConfigurationService.getIdentifier()).thenReturn(PROXY_SERVICE_ID);
|
||||
when(proxyConfigurationService.getConfiguration()).thenReturn(proxyConfiguration);
|
||||
|
||||
mockWebServer.enqueue(new MockResponse()
|
||||
.setResponseCode(HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED.getCode())
|
||||
.setHeader(PROXY_AUTHENTICATE_HEADER, PROXY_AUTHENTICATE_BASIC_REALM)
|
||||
);
|
||||
|
||||
runner.addControllerService(PROXY_SERVICE_ID, proxyConfigurationService);
|
||||
runner.enableControllerService(proxyConfigurationService);
|
||||
|
||||
runner.setProperty(provider, ProxyConfigurationService.PROXY_CONFIGURATION_SERVICE, PROXY_SERVICE_ID);
|
||||
runner.enableControllerService(provider);
|
||||
|
||||
final WebClientService webClientService = provider.getWebClientService();
|
||||
|
||||
assertNotNull(webClientService);
|
||||
|
||||
assertGetUriCompleted(webClientService);
|
||||
|
||||
final RecordedRequest proxyAuthorizationRequest = mockWebServer.takeRequest();
|
||||
final String proxyAuthorization = proxyAuthorizationRequest.getHeader(PROXY_AUTHORIZATION_HEADER);
|
||||
final String credentials = Credentials.basic(username, password);
|
||||
assertEquals(credentials, proxyAuthorization);
|
||||
}
|
||||
|
||||
private void assertGetUriCompleted(final WebClientService webClientService) throws InterruptedException {
|
||||
final URI uri = mockWebServer.url(ROOT_PATH).newBuilder().host(LOCALHOST).build().uri();
|
||||
|
||||
final HttpResponseStatus httpResponseStatus = HttpResponseStatus.OK;
|
||||
final MockResponse mockResponse = new MockResponse().setResponseCode(httpResponseStatus.getCode());
|
||||
mockWebServer.enqueue(mockResponse);
|
||||
|
||||
final HttpResponseEntity httpResponseEntity = webClientService.get().uri(uri).retrieve();
|
||||
|
||||
assertNotNull(httpResponseEntity);
|
||||
assertEquals(httpResponseStatus.getCode(), httpResponseEntity.statusCode());
|
||||
|
||||
final RecordedRequest request = mockWebServer.takeRequest();
|
||||
final HttpUrl requestUrl = request.getRequestUrl();
|
||||
assertNotNull(requestUrl);
|
||||
|
||||
final URI requestUri = requestUrl.uri();
|
||||
assertEquals(uri.getPort(), requestUri.getPort());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF 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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-standard-services</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-web-client-provider-bundle</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>nifi-web-client-provider-api</module>
|
||||
<module>nifi-web-client-provider-service</module>
|
||||
<module>nifi-web-client-provider-service-nar</module>
|
||||
</modules>
|
||||
</project>
|
|
@ -54,5 +54,6 @@
|
|||
<module>nifi-hadoop-dbcp-service-bundle</module>
|
||||
<module>nifi-kerberos-user-service-api</module>
|
||||
<module>nifi-kerberos-user-service-bundle</module>
|
||||
<module>nifi-web-client-provider-bundle</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
Loading…
Reference in New Issue