├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── lombok.config
├── okhttp-spring-boot-autoconfigure
├── src
│ ├── test
│ │ ├── resources
│ │ │ └── keystore.jks
│ │ └── java
│ │ │ └── io
│ │ │ └── freefair
│ │ │ └── spring
│ │ │ └── okhttp
│ │ │ ├── DurationPropertiesIT.java
│ │ │ ├── metrics
│ │ │ └── OkHttpMetricsAutoConfigurationTest.java
│ │ │ ├── client
│ │ │ └── OkHttpClientRequestFactoryTest.java
│ │ │ ├── ssl
│ │ │ └── SslBundleTest.java
│ │ │ ├── OkHttp3AutoConfigurationIT.java
│ │ │ ├── OkHttpRestTemplateAutoConfigurationTest.java
│ │ │ ├── OkHttpRestClientAutoConfigurationTest.java
│ │ │ ├── logging
│ │ │ └── OkHttp3LoggingInterceptorAutoConfigurationTest.java
│ │ │ └── OkHttp3AutoConfigurationTest.java
│ └── main
│ │ ├── java
│ │ └── io
│ │ │ └── freefair
│ │ │ └── spring
│ │ │ └── okhttp
│ │ │ ├── OkHttp3Configurer.java
│ │ │ ├── autoconfigure
│ │ │ ├── logging
│ │ │ │ ├── OkHttp3LoggingInterceptorProperties.java
│ │ │ │ └── OkHttp3LoggingInterceptorAutoConfiguration.java
│ │ │ ├── OkHttpClientRequestFactoryBuilderAutoConfiguration.java
│ │ │ ├── metrics
│ │ │ │ ├── OkHttpMetricsProperties.java
│ │ │ │ └── OkHttpMetricsAutoConfiguration.java
│ │ │ ├── OkHttpProperties.java
│ │ │ ├── OkHttpClientRequestFactoryBuilder.java
│ │ │ └── OkHttp3AutoConfiguration.java
│ │ │ ├── ApplicationInterceptor.java
│ │ │ └── NetworkInterceptor.java
│ │ └── resources
│ │ └── META-INF
│ │ └── spring
│ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── build.gradle
├── okhttp-spring-client
├── src
│ ├── main
│ │ └── java
│ │ │ └── io
│ │ │ └── freefair
│ │ │ └── spring
│ │ │ └── okhttp
│ │ │ ├── client
│ │ │ ├── package-info.java
│ │ │ ├── OkHttpClientRequestFactory.java
│ │ │ ├── StreamingBodyRequestBody.java
│ │ │ ├── OkHttpClientResponse.java
│ │ │ └── OkHttpClientRequest.java
│ │ │ ├── OkHttpUtils.java
│ │ │ ├── OkHttpResponseResource.java
│ │ │ ├── ResourceRequestBody.java
│ │ │ └── OkHttpResponseBodyResource.java
│ └── test
│ │ └── java
│ │ └── io
│ │ └── freefair
│ │ └── spring
│ │ └── okhttp
│ │ ├── TestServer.java
│ │ ├── OkHttpUtilsTest.java
│ │ ├── ResourceRequestBodyTest.java
│ │ └── OkHttpResponseResourceTest.java
└── build.gradle
├── .editorconfig
├── starters
├── okhttp-spring-boot-starter
│ └── build.gradle
├── okhttp4-spring-boot-starter
│ ├── src
│ │ └── test
│ │ │ └── java
│ │ │ └── io
│ │ │ └── freefair
│ │ │ └── okhttp
│ │ │ └── starter4
│ │ │ ├── OkHttp4TestApplication.java
│ │ │ └── OkHttp4Test.java
│ └── build.gradle
├── okhttp5-spring-boot-starter
│ ├── src
│ │ └── test
│ │ │ └── java
│ │ │ └── io
│ │ │ └── freefair
│ │ │ └── okhttp
│ │ │ └── starter5
│ │ │ ├── OkHttp5TestApplication.java
│ │ │ └── OkHttp5Test.java
│ └── build.gradle
└── build.gradle
├── .github
├── workflows
│ ├── gradle-wrapper-validation.yml
│ ├── dependency-submission.yml
│ ├── gradle-deploy.yml
│ ├── gradle.yml
│ └── gradle-publish.yml
├── CONTRIBUTING.md
└── dependabot.yml
├── settings.gradle
├── okhttp-spring-boot-dependencies
└── build.gradle
├── LICENSE
├── README.md
├── .gitignore
├── config
└── checkstyle
│ └── checkstyle.xml
├── gradlew.bat
└── gradlew
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.parallel=true
2 | org.gradle.caching=true
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freefair/okhttp-spring-boot/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/lombok.config:
--------------------------------------------------------------------------------
1 | config.stopBubbling = true
2 | lombok.addLombokGeneratedAnnotation = true
3 | lombok.nonnull.exceptiontype=IllegalArgumentException
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/test/resources/keystore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freefair/okhttp-spring-boot/HEAD/okhttp-spring-boot-autoconfigure/src/test/resources/keystore.jks
--------------------------------------------------------------------------------
/okhttp-spring-client/src/main/java/io/freefair/spring/okhttp/client/package-info.java:
--------------------------------------------------------------------------------
1 | @NonNullApi
2 | package io.freefair.spring.okhttp.client;
3 |
4 | import org.springframework.lang.NonNullApi;
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*.{java,gradle}]
7 | charset = utf-8
8 | insert_final_newline = true
9 | indent_style = space
10 | trim_trailing_whitespace = true
11 |
--------------------------------------------------------------------------------
/starters/okhttp-spring-boot-starter/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "io.freefair.maven-publish-java"
2 | apply plugin: "java-library"
3 |
4 | description = "OkHttp Spring Boot Starter"
5 |
6 | dependencies {
7 | api project(':starters:okhttp4-spring-boot-starter')
8 | }
9 |
--------------------------------------------------------------------------------
/starters/okhttp4-spring-boot-starter/src/test/java/io/freefair/okhttp/starter4/OkHttp4TestApplication.java:
--------------------------------------------------------------------------------
1 | package io.freefair.okhttp.starter4;
2 |
3 | import org.springframework.boot.autoconfigure.SpringBootApplication;
4 |
5 | @SpringBootApplication
6 | public class OkHttp4TestApplication {
7 | }
8 |
--------------------------------------------------------------------------------
/starters/okhttp5-spring-boot-starter/src/test/java/io/freefair/okhttp/starter5/OkHttp5TestApplication.java:
--------------------------------------------------------------------------------
1 | package io.freefair.okhttp.starter5;
2 |
3 | import org.springframework.boot.autoconfigure.SpringBootApplication;
4 |
5 | @SpringBootApplication
6 | public class OkHttp5TestApplication {
7 | }
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/.github/workflows/gradle-wrapper-validation.yml:
--------------------------------------------------------------------------------
1 | name: "Validate Gradle Wrapper"
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | validation:
9 | name: "Validation"
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v6
13 | - uses: gradle/actions/wrapper-validation@v5
--------------------------------------------------------------------------------
/starters/build.gradle:
--------------------------------------------------------------------------------
1 | subprojects { Project starterProject ->
2 | starterProject.apply plugin: "java-library"
3 |
4 | starterProject.tasks.named('jar', Jar) {
5 | manifest {
6 | attributes 'Spring-Boot-Jar-Type': 'dependencies-starter'
7 | }
8 | }
9 |
10 | test.useJUnitPlatform()
11 | }
12 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/main/java/io/freefair/spring/okhttp/OkHttp3Configurer.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import okhttp3.OkHttpClient;
4 |
5 | /**
6 | * @author Lars Grefer
7 | */
8 | @FunctionalInterface
9 | public interface OkHttp3Configurer {
10 |
11 | void configure(OkHttpClient.Builder object);
12 | }
13 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
--------------------------------------------------------------------------------
1 | io.freefair.spring.okhttp.autoconfigure.OkHttp3AutoConfiguration
2 | io.freefair.spring.okhttp.autoconfigure.OkHttpClientRequestFactoryBuilderAutoConfiguration
3 | io.freefair.spring.okhttp.autoconfigure.metrics.OkHttpMetricsAutoConfiguration
4 | io.freefair.spring.okhttp.autoconfigure.logging.OkHttp3LoggingInterceptorAutoConfiguration
--------------------------------------------------------------------------------
/.github/workflows/dependency-submission.yml:
--------------------------------------------------------------------------------
1 | name: Dependency Submission
2 |
3 | on:
4 | push:
5 | branches: [ 'main' ]
6 |
7 | permissions:
8 | contents: write
9 |
10 | jobs:
11 | dependency-submission:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v6
15 | - uses: actions/setup-java@v5
16 | with:
17 | distribution: temurin
18 | java-version: 17
19 |
20 | - name: Generate and submit dependency graph
21 | uses: gradle/actions/dependency-submission@v5
22 |
--------------------------------------------------------------------------------
/starters/okhttp4-spring-boot-starter/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "io.freefair.maven-publish-java"
2 | apply plugin: "java-library"
3 |
4 | description = "OkHttp 4 Spring Boot Starter"
5 |
6 | dependencies {
7 | api platform(project(":okhttp-spring-boot-dependencies"))
8 |
9 | api 'org.springframework.boot:spring-boot-starter'
10 | api project(':okhttp-spring-boot-autoconfigure')
11 | api project(':okhttp-spring-client')
12 | api 'com.squareup.okhttp3:okhttp'
13 |
14 | testImplementation 'org.springframework.boot:spring-boot-starter-test'
15 | }
16 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution guidelines
2 |
3 | ## Branching
4 |
5 | When starting to work on a pull request make sure to start your work on the correct branch.
6 | This repository uses the same branching model as _Spring Boot_ and the _Spring Framework_.
7 |
8 | The `master` branch always contains the latest _bleeding edge_ code, while the older versions live in their own branches.
9 |
10 | __For example:__ If you wan't to fix a bug in Version `1.x.y`, make sure to start your work on the `1.x` branch
11 | and not on the `master` branch. When opening the pull request make also sure, to set `1.x` as _base_.
12 |
--------------------------------------------------------------------------------
/okhttp-spring-client/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java-library'
3 | id "io.freefair.lombok"
4 | id "io.freefair.maven-publish-java"
5 | }
6 |
7 | description = "OkHttp Spring Client"
8 |
9 | dependencies {
10 | implementation platform(project(":okhttp-spring-boot-dependencies"))
11 |
12 | api "com.squareup.okhttp3:okhttp"
13 | api "org.springframework:spring-web"
14 |
15 | testImplementation "org.springframework.boot:spring-boot-starter-test"
16 | testImplementation "org.springframework.boot:spring-boot-starter-web"
17 | }
18 |
19 | tasks.named("test", Test) {
20 | useJUnitPlatform()
21 | }
22 |
--------------------------------------------------------------------------------
/starters/okhttp5-spring-boot-starter/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "io.freefair.maven-publish-java"
2 | apply plugin: "java-library"
3 |
4 | description = "OkHttp 5 Spring Boot Starter"
5 |
6 | dependencies {
7 | api platform(project(":okhttp-spring-boot-dependencies"))
8 | api platform('com.squareup.okhttp3:okhttp-bom:5.3.2')
9 |
10 | api 'org.springframework.boot:spring-boot-starter'
11 | api project(':okhttp-spring-boot-autoconfigure')
12 | api project(':okhttp-spring-client')
13 | api 'com.squareup.okhttp3:okhttp'
14 |
15 | testImplementation 'org.springframework.boot:spring-boot-starter-test'
16 | }
17 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/main/java/io/freefair/spring/okhttp/autoconfigure/logging/OkHttp3LoggingInterceptorProperties.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.autoconfigure.logging;
2 |
3 | import lombok.Data;
4 | import okhttp3.logging.HttpLoggingInterceptor;
5 | import org.springframework.boot.context.properties.ConfigurationProperties;
6 |
7 | /**
8 | * @author Lars Grefer
9 | */
10 | @Data
11 | @ConfigurationProperties(prefix = "okhttp.logging")
12 | public class OkHttp3LoggingInterceptorProperties {
13 |
14 | /**
15 | * The level at which the HttpLoggingInterceptor logs.
16 | */
17 | private HttpLoggingInterceptor.Level level = HttpLoggingInterceptor.Level.NONE;
18 | }
19 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/main/java/io/freefair/spring/okhttp/ApplicationInterceptor.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import org.springframework.beans.factory.annotation.Qualifier;
4 |
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.Target;
7 |
8 | import static java.lang.annotation.ElementType.*;
9 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
10 |
11 | /**
12 | * A {@link Qualifier} annotation for {@link okhttp3.Interceptor OkHttp3-Interceptors}.
13 | *
14 | * @author Lars Grefer
15 | * @see NetworkInterceptor
16 | */
17 | @Target({METHOD, FIELD, CONSTRUCTOR, TYPE})
18 | @Retention(RUNTIME)
19 | @Qualifier
20 | public @interface ApplicationInterceptor {
21 | }
22 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/main/java/io/freefair/spring/okhttp/NetworkInterceptor.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import org.springframework.beans.factory.annotation.Qualifier;
4 |
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.Target;
7 |
8 | import static java.lang.annotation.ElementType.*;
9 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
10 |
11 | /**
12 | * A {@link Qualifier} annotation for {@link okhttp3.Interceptor OkHttp3-Interceptors}.
13 | *
14 | * @author Lars Grefer
15 | * @see ApplicationInterceptor
16 | */
17 | @SuppressWarnings("WeakerAccess")
18 | @Target({METHOD, FIELD, CONSTRUCTOR, TYPE})
19 | @Retention(RUNTIME)
20 | @Qualifier
21 | public @interface NetworkInterceptor {
22 | }
23 |
--------------------------------------------------------------------------------
/.github/workflows/gradle-deploy.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Gradle
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
3 |
4 | name: Deploy Docs
5 |
6 | on:
7 | push:
8 | branches-ignore:
9 | - 'dependabot**'
10 |
11 | jobs:
12 | deploy-docs:
13 | runs-on: ubuntu-latest
14 | env:
15 | ORG_GRADLE_PROJECT_freefairDocsUser: ${{ secrets.FREEFAIR_DOCS_USER }}
16 | ORG_GRADLE_PROJECT_freefairDocsPass: ${{ secrets.FREEFAIR_DOCS_PASS }}
17 | steps:
18 | - uses: actions/checkout@v6
19 | - name: Set up JDK 17
20 | uses: actions/setup-java@v5
21 | with:
22 | java-version: 17
23 | distribution: 'temurin'
24 | - name: Setup Gradle
25 | uses: gradle/actions/setup-gradle@v5
26 | - run: ./gradlew uploadDocumentation -s
27 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "gradle"
9 | directory: "/"
10 | target-branch: "3.4.x"
11 | schedule:
12 | interval: "daily"
13 | - package-ecosystem: "gradle"
14 | directory: "/"
15 | target-branch: "3.5.x"
16 | schedule:
17 | interval: "daily"
18 | - package-ecosystem: "gradle"
19 | directory: "/"
20 | target-branch: "4.0.x"
21 | schedule:
22 | interval: "daily"
23 |
24 | # Maintain dependencies for GitHub Actions
25 | - package-ecosystem: "github-actions"
26 | directory: "/"
27 | schedule:
28 | interval: "weekly"
29 |
--------------------------------------------------------------------------------
/okhttp-spring-client/src/main/java/io/freefair/spring/okhttp/client/OkHttpClientRequestFactory.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.client;
2 |
3 | import okhttp3.OkHttpClient;
4 | import org.springframework.http.HttpMethod;
5 | import org.springframework.http.client.ClientHttpRequest;
6 | import org.springframework.http.client.ClientHttpRequestFactory;
7 | import org.springframework.lang.NonNull;
8 |
9 | import java.net.URI;
10 |
11 | /**
12 | * OkHttp based {@link ClientHttpRequestFactory} implementation.
13 | *
14 | * Serves as replacement for the deprecated {@link org.springframework.http.client.OkHttp3ClientHttpRequestFactory}.
15 | *
16 | * @author Lars Grefer
17 | */
18 | public record OkHttpClientRequestFactory(@NonNull OkHttpClient okHttpClient) implements ClientHttpRequestFactory {
19 |
20 | @Override
21 | public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
22 | return new OkHttpClientRequest(okHttpClient, uri, httpMethod);
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/.github/workflows/gradle.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Gradle
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
3 |
4 | name: Java CI with Gradle
5 |
6 | on:
7 | - push
8 | - pull_request
9 |
10 | jobs:
11 | build:
12 | name: build
13 | runs-on: ubuntu-latest
14 | strategy:
15 | fail-fast: false
16 | matrix:
17 | java: [ 17 ]
18 | steps:
19 | - uses: actions/checkout@v6
20 |
21 | - name: Set up JDK ${{ matrix.java }}
22 | uses: actions/setup-java@v5
23 | with:
24 | java-version: ${{ matrix.java }}
25 | distribution: 'temurin'
26 |
27 | - name: Setup Gradle
28 | uses: gradle/actions/setup-gradle@v5
29 |
30 | - run: ./gradlew -V assemble -s
31 | - run: ./gradlew -V check -s
32 | - name: Upload Coverage Reports
33 | run: |
34 | ./gradlew jacocoTestReport
35 | bash <(curl -s https://codecov.io/bash)
36 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | maven { url = 'https://repo.spring.io/milestone' }
5 | }
6 | }
7 |
8 | plugins {
9 | id "io.freefair.settings.plugin-versions" version "9.1.0"
10 | id "com.gradle.develocity" version "4.3"
11 | }
12 |
13 | boolean isCiServer = System.getenv().containsKey("CI")
14 |
15 | develocity {
16 | buildScan {
17 | publishing.onlyIf { isCiServer }
18 | termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use"
19 | termsOfUseAgree = "yes"
20 | }
21 | }
22 |
23 | dependencyResolutionManagement {
24 | repositories {
25 | mavenCentral()
26 | maven { url = 'https://repo.spring.io/milestone' }
27 | }
28 | }
29 |
30 | include 'okhttp-spring-client'
31 |
32 | include 'okhttp-spring-boot-dependencies'
33 | include 'okhttp-spring-boot-autoconfigure'
34 | include 'starters:okhttp-spring-boot-starter'
35 | include 'starters:okhttp4-spring-boot-starter'
36 | include 'starters:okhttp5-spring-boot-starter'
37 |
38 | rootProject.name = 'okhttp-spring-boot'
39 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-dependencies/build.gradle:
--------------------------------------------------------------------------------
1 | import org.springframework.boot.gradle.plugin.SpringBootPlugin
2 |
3 | plugins {
4 | id "java-platform"
5 | id "maven-publish"
6 | }
7 |
8 | javaPlatform {
9 | allowDependencies()
10 | }
11 |
12 | dependencies {
13 | api platform(SpringBootPlugin.BOM_COORDINATES)
14 | api platform('com.squareup.okhttp3:okhttp-bom:4.12.0')
15 |
16 | constraints {
17 | api project(":okhttp-spring-client")
18 | api project(":okhttp-spring-boot-autoconfigure")
19 | api project(":starters:okhttp-spring-boot-starter")
20 | api project(":starters:okhttp4-spring-boot-starter")
21 | api project(":starters:okhttp5-spring-boot-starter")
22 | }
23 | }
24 |
25 | publishing {
26 | publications {
27 | bom(MavenPublication) {
28 | from components.javaPlatform
29 |
30 | pom {
31 | name = 'OkHttp Spring Boot Dependencies'
32 | description = 'OkHttp Spring Boot Dependencies'
33 | }
34 |
35 | signing.sign it
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/okhttp-spring-client/src/test/java/io/freefair/spring/okhttp/TestServer.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import jakarta.servlet.http.HttpServletRequest;
4 | import jakarta.servlet.http.HttpServletResponse;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 | import org.springframework.util.FileCopyUtils;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RestController;
9 |
10 | import java.io.IOException;
11 |
12 | @SpringBootApplication
13 | @RestController
14 | public class TestServer {
15 |
16 | @RequestMapping("/echo")
17 | public void echo(HttpServletRequest request, HttpServletResponse response) throws IOException {
18 |
19 | request.getHeaderNames().asIterator().forEachRemaining(header -> {
20 | request.getHeaders(header).asIterator().forEachRemaining(value -> {
21 | response.addHeader(header, value);
22 | });
23 | });
24 |
25 | FileCopyUtils.copy(request.getInputStream(), response.getOutputStream());
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016-2023 Lars Grefer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/okhttp-spring-client/src/main/java/io/freefair/spring/okhttp/OkHttpUtils.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import kotlin.Pair;
4 | import lombok.experimental.UtilityClass;
5 | import okhttp3.Headers;
6 | import org.springframework.http.HttpHeaders;
7 | import org.springframework.util.Assert;
8 |
9 | @UtilityClass
10 | public class OkHttpUtils {
11 |
12 | public static HttpHeaders toSpringHeaders(Headers okhttpHeaders) {
13 | Assert.notNull(okhttpHeaders, "Headers must not be null");
14 | HttpHeaders springHeaders = new HttpHeaders();
15 |
16 | for (Pair extends String, ? extends String> okhttpHeader : okhttpHeaders) {
17 | springHeaders.add(okhttpHeader.getFirst(), okhttpHeader.getSecond());
18 | }
19 |
20 | return springHeaders;
21 | }
22 |
23 | public static Headers toOkHttpHeaders(HttpHeaders springHeaders) {
24 | Headers.Builder builder = new Headers.Builder();
25 |
26 | springHeaders.forEach((name, values) -> {
27 | values.forEach(value -> builder.add(name, value));
28 | });
29 |
30 | return builder.build();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spring Boot Starters for [OkHttp](http://square.github.io/okhttp/)
2 |
3 | 
4 | [](https://github.com/freefair/okhttp-spring-boot/blob/master/LICENSE)
5 | [](https://codecov.io/gh/freefair/okhttp-spring-boot)
6 | [](https://travis-ci.com/freefair/okhttp-spring-boot)
7 |
8 | ## Available Starters
9 |
10 | - `okhttp4-spring-boot-starter`
11 | - `okhttp5-spring-boot-starter`
12 | - `okhttp-spring-boot-starter` (Alias for `okhttp4-spring-boot-starter`)
13 |
14 | ## Using Gradle
15 | ```gradle
16 | dependencies {
17 | compile "io.freefair.okhttp-spring-boot:okhttp-spring-boot-starter:$version"
18 | }
19 | ```
20 | ## Using Maven
21 | ```xml
22 |
23 | io.freefair.okhttp-spring-boot
24 | okhttp-spring-boot-starter
25 | ${version}
26 |
27 | ```
28 |
--------------------------------------------------------------------------------
/.github/workflows/gradle-publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish Release
2 |
3 | on:
4 | release:
5 | types: [ created ]
6 |
7 | jobs:
8 | publish:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v6
12 | - name: Set up JDK 17
13 | uses: actions/setup-java@v5
14 | with:
15 | java-version: 17
16 | distribution: 'temurin'
17 | - name: Setup Gradle
18 | uses: gradle/actions/setup-gradle@v5
19 | - name: Publish local
20 | run: ./gradlew publish
21 | env:
22 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.FREEFAIR_SIGNING_KEY }}
23 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.FREEFAIR_SIGNING_PASSWORD }}
24 | - name: Publish package
25 | run: ./gradlew --no-parallel uploadDocumentation jreleaserDeploy
26 | env:
27 | JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.MAVENCENTRAL_USERNAME }}
28 | JRELEASER_MAVENCENTRAL_TOKEN: ${{ secrets.MAVENCENTRAL_PASSWORD }}
29 | JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 | ORG_GRADLE_PROJECT_freefairDocsUser: ${{ secrets.FREEFAIR_DOCS_USER }}
31 | ORG_GRADLE_PROJECT_freefairDocsPass: ${{ secrets.FREEFAIR_DOCS_PASS }}
32 |
--------------------------------------------------------------------------------
/starters/okhttp4-spring-boot-starter/src/test/java/io/freefair/okhttp/starter4/OkHttp4Test.java:
--------------------------------------------------------------------------------
1 | package io.freefair.okhttp.starter4;
2 |
3 | import okhttp3.OkHttpClient;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.test.context.SpringBootTest;
7 | import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
8 | import org.springframework.context.ConfigurableApplicationContext;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 |
12 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
13 | public class OkHttp4Test {
14 |
15 | @Autowired
16 | private ConfigurableApplicationContext applicationContext;
17 |
18 | @Test
19 | void contextLoads() {
20 | assertThat(applicationContext).isNotNull();
21 | }
22 |
23 | @Test
24 | void hasClientBean() {
25 | AssertableApplicationContext context = AssertableApplicationContext.get(() -> applicationContext);
26 |
27 | assertThat(context).hasSingleBean(OkHttpClient.class);
28 | OkHttpClient okHttpClient = context.getBean(OkHttpClient.class);
29 |
30 | assertThat(okHttpClient).isNotNull();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/starters/okhttp5-spring-boot-starter/src/test/java/io/freefair/okhttp/starter5/OkHttp5Test.java:
--------------------------------------------------------------------------------
1 | package io.freefair.okhttp.starter5;
2 |
3 | import okhttp3.OkHttpClient;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.test.context.SpringBootTest;
7 | import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
8 | import org.springframework.context.ConfigurableApplicationContext;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 |
12 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
13 | public class OkHttp5Test {
14 |
15 | @Autowired
16 | private ConfigurableApplicationContext applicationContext;
17 |
18 | @Test
19 | void contextLoads() {
20 | assertThat(applicationContext).isNotNull();
21 | }
22 |
23 | @Test
24 | void hasClientBean() {
25 | AssertableApplicationContext context = AssertableApplicationContext.get(() -> applicationContext);
26 |
27 | assertThat(context).hasSingleBean(OkHttpClient.class);
28 | OkHttpClient okHttpClient = context.getBean(OkHttpClient.class);
29 |
30 | assertThat(okHttpClient).isNotNull();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/okhttp-spring-client/src/test/java/io/freefair/spring/okhttp/OkHttpUtilsTest.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import okhttp3.Headers;
4 | import org.assertj.core.api.Assertions;
5 | import org.junit.jupiter.api.Test;
6 | import org.springframework.http.HttpHeaders;
7 |
8 | import java.nio.charset.StandardCharsets;
9 | import java.time.Instant;
10 | import java.time.temporal.ChronoUnit;
11 | import java.util.List;
12 |
13 | import static org.assertj.core.api.Assertions.assertThat;
14 |
15 | class OkHttpUtilsTest {
16 |
17 |
18 | @Test
19 | void headerConversion() {
20 |
21 | HttpHeaders springHeaders = new HttpHeaders();
22 | Instant date = Instant.now();
23 |
24 | springHeaders.set("key1", "value1");
25 | springHeaders.setAcceptCharset(List.of(StandardCharsets.UTF_8));
26 | springHeaders.setDate(date);
27 |
28 | Headers okHttpHeaders = OkHttpUtils.toOkHttpHeaders(springHeaders);
29 |
30 | assertThat(okHttpHeaders.get("key1")).isEqualTo("value1");
31 | assertThat(okHttpHeaders.getInstant("Date")).isCloseTo(date, Assertions.within(1, ChronoUnit.SECONDS));
32 | assertThat(okHttpHeaders.get("Accept-Charset")).isEqualToIgnoringCase("UTF-8");
33 |
34 | assertThat(OkHttpUtils.toSpringHeaders(okHttpHeaders)).isEqualTo(springHeaders);
35 |
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/okhttp-spring-client/src/test/java/io/freefair/spring/okhttp/ResourceRequestBodyTest.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import okhttp3.MediaType;
4 | import okio.ByteString;
5 | import org.junit.jupiter.api.Test;
6 | import org.springframework.core.io.ByteArrayResource;
7 | import org.springframework.util.MimeType;
8 | import org.springframework.util.MimeTypeUtils;
9 |
10 | import java.nio.charset.StandardCharsets;
11 |
12 | import static org.assertj.core.api.Assertions.assertThat;
13 |
14 |
15 | class ResourceRequestBodyTest {
16 |
17 |
18 | @Test
19 | void testContentTypes() {
20 |
21 | ByteArrayResource resource = new ByteArrayResource(ByteString.encodeUtf8("hello world").toByteArray());
22 |
23 | assertThat(new ResourceRequestBody(resource).contentType())
24 | .isNull();
25 |
26 | assertThat(new ResourceRequestBody(resource, MimeTypeUtils.IMAGE_PNG).contentType().toString())
27 | .isEqualTo("image/png");
28 |
29 | assertThat(new ResourceRequestBody(resource, new MimeType("application", "foo", StandardCharsets.UTF_16)).contentType().toString())
30 | .isEqualTo("application/foo;charset=UTF-16");
31 |
32 | assertThat(new ResourceRequestBody(resource, MediaType.parse("text/plain")).contentType().toString())
33 | .isEqualTo("text/plain");
34 |
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/okhttp-spring-client/src/main/java/io/freefair/spring/okhttp/client/StreamingBodyRequestBody.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.client;
2 |
3 | import lombok.RequiredArgsConstructor;
4 | import okhttp3.MediaType;
5 | import okhttp3.RequestBody;
6 | import okio.BufferedSink;
7 | import org.jetbrains.annotations.NotNull;
8 | import org.springframework.http.StreamingHttpOutputMessage;
9 | import org.springframework.lang.Nullable;
10 |
11 | import java.io.IOException;
12 |
13 | /**
14 | * {@link StreamingHttpOutputMessage.Body} based {@link RequestBody} implementation.
15 | *
16 | * @author Lars Grefer
17 | * @see OkHttpClientRequest
18 | */
19 | @RequiredArgsConstructor
20 | class StreamingBodyRequestBody extends RequestBody {
21 |
22 | private final StreamingHttpOutputMessage.Body streamingBody;
23 |
24 | private final MediaType contentType;
25 |
26 | @Nullable
27 | private final long contentLength;
28 |
29 | @Nullable
30 | @Override
31 | public MediaType contentType() {
32 | return contentType;
33 | }
34 |
35 | @Override
36 | public long contentLength() {
37 | return contentLength;
38 | }
39 |
40 | @Override
41 | public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {
42 | streamingBody.writeTo(bufferedSink.outputStream());
43 | }
44 |
45 | @Override
46 | public boolean isOneShot() {
47 | return !streamingBody.repeatable();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/main/java/io/freefair/spring/okhttp/autoconfigure/OkHttpClientRequestFactoryBuilderAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.autoconfigure;
2 |
3 | import io.freefair.spring.okhttp.client.OkHttpClientRequestFactory;
4 | import okhttp3.OkHttpClient;
5 | import org.springframework.boot.autoconfigure.AutoConfiguration;
6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
8 | import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
9 | import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
10 | import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
11 | import org.springframework.context.annotation.Bean;
12 |
13 | /**
14 | * @author Lars Grefer
15 | * @see RestTemplateAutoConfiguration
16 | */
17 | @AutoConfiguration(before = RestTemplateAutoConfiguration.class, after = HttpMessageConvertersAutoConfiguration.class)
18 | @ConditionalOnClass({ClientHttpRequestFactoryBuilder.class, OkHttpClientRequestFactory.class})
19 | public class OkHttpClientRequestFactoryBuilderAutoConfiguration {
20 |
21 | @Bean
22 | @ConditionalOnMissingBean(ClientHttpRequestFactoryBuilder.class)
23 | public OkHttpClientRequestFactoryBuilder okHttpClientRequestFactoryBuilder(OkHttpClient okHttpClient) {
24 | return new OkHttpClientRequestFactoryBuilder(okHttpClient);
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/test/java/io/freefair/spring/okhttp/DurationPropertiesIT.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import io.freefair.spring.okhttp.autoconfigure.OkHttpProperties;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.SpringBootConfiguration;
7 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
8 | import org.springframework.boot.test.context.SpringBootTest;
9 |
10 | import java.time.Duration;
11 |
12 | import static org.assertj.core.api.Assertions.assertThat;
13 |
14 |
15 | /**
16 | * @author Lars Grefer
17 | */
18 | @SpringBootTest(
19 | properties = {
20 | "okhttp.connectTimeout=10s",
21 | "okhttp.readTimeout=1d",
22 | "okhttp.writeTimeout=5"
23 | }
24 | )
25 | public class DurationPropertiesIT {
26 |
27 | @Autowired
28 | private OkHttpProperties okHttpProperties;
29 |
30 | @Test
31 | public void getConnectTimeout() {
32 | assertThat(okHttpProperties.getConnectTimeout()).isEqualTo(Duration.ofSeconds(10));
33 | }
34 |
35 | @Test
36 | public void getReadTimeout() {
37 | assertThat(okHttpProperties.getReadTimeout()).isEqualTo(Duration.ofDays(1));
38 | }
39 |
40 | @Test
41 | public void getWriteTimeout() {
42 | assertThat(okHttpProperties.getWriteTimeout()).isEqualTo(Duration.ofMillis(5));
43 | }
44 |
45 | @SpringBootConfiguration
46 | @EnableConfigurationProperties(OkHttpProperties.class)
47 | static class Config {
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/test/java/io/freefair/spring/okhttp/metrics/OkHttpMetricsAutoConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.metrics;
2 |
3 | import io.freefair.spring.okhttp.autoconfigure.metrics.OkHttpMetricsAutoConfiguration;
4 | import io.micrometer.core.instrument.MeterRegistry;
5 | import okhttp3.EventListener;
6 | import org.junit.jupiter.api.BeforeEach;
7 | import org.junit.jupiter.api.Test;
8 | import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
9 | import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
10 | import org.springframework.boot.autoconfigure.AutoConfigurations;
11 | import org.springframework.boot.test.context.runner.ApplicationContextRunner;
12 |
13 | import static org.assertj.core.api.Assertions.assertThat;
14 |
15 | class OkHttpMetricsAutoConfigurationTest {
16 |
17 | private ApplicationContextRunner applicationContextRunner;
18 |
19 | @BeforeEach
20 | public void setUp() {
21 | applicationContextRunner = new ApplicationContextRunner()
22 | .withConfiguration(AutoConfigurations.of(
23 | OkHttpMetricsAutoConfiguration.class,
24 | MetricsAutoConfiguration.class,
25 | CompositeMeterRegistryAutoConfiguration.class
26 | ));
27 | }
28 |
29 | @Test
30 | public void testEventListener() {
31 | applicationContextRunner.run(context -> {
32 | assertThat(context).hasSingleBean(MeterRegistry.class);
33 | assertThat(context).hasSingleBean(EventListener.class);
34 | });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/build.gradle:
--------------------------------------------------------------------------------
1 | import org.springframework.boot.gradle.plugin.SpringBootPlugin
2 |
3 | plugins {
4 | id "checkstyle"
5 | id "jacoco"
6 | id "io.freefair.lombok"
7 | id "java-library"
8 | id "io.freefair.maven-optional"
9 | id "io.freefair.maven-publish-java"
10 | }
11 |
12 | description = "OkHttp Spring Boot AutoConfigure"
13 |
14 | tasks.named('compileJava') {
15 | inputs.files(processResources)
16 | }
17 |
18 | tasks.named('jacocoTestReport', JacocoReport) {
19 | reports.xml.required = true
20 | }
21 |
22 | dependencies {
23 | api platform(SpringBootPlugin.BOM_COORDINATES)
24 | annotationProcessor platform(SpringBootPlugin.BOM_COORDINATES)
25 |
26 | annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
27 | annotationProcessor 'org.springframework.boot:spring-boot-autoconfigure-processor'
28 |
29 | api 'org.springframework.boot:spring-boot-autoconfigure'
30 | implementation 'org.slf4j:slf4j-api'
31 |
32 | optional project(':okhttp-spring-client')
33 | optional 'org.springframework:spring-web'
34 | optional 'org.springframework.boot:spring-boot-starter-actuator'
35 |
36 | optional platform('com.squareup.okhttp3:okhttp-bom:4.12.0')
37 | optional 'com.squareup.okhttp3:okhttp'
38 | optional 'com.squareup.okhttp3:logging-interceptor'
39 |
40 | testImplementation 'org.springframework.boot:spring-boot-starter-web'
41 | testImplementation('org.springframework.boot:spring-boot-starter-test') {
42 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
43 | }
44 | }
45 |
46 | tasks.named('test', Test) {
47 | useJUnitPlatform()
48 | }
49 |
--------------------------------------------------------------------------------
/okhttp-spring-client/src/main/java/io/freefair/spring/okhttp/OkHttpResponseResource.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import okhttp3.Response;
4 | import org.springframework.http.ContentDisposition;
5 | import org.springframework.http.HttpHeaders;
6 | import org.springframework.http.HttpMessage;
7 | import org.springframework.lang.NonNull;
8 | import org.springframework.lang.Nullable;
9 | import org.springframework.util.StringUtils;
10 |
11 | import java.net.URI;
12 | import java.net.URL;
13 |
14 | public class OkHttpResponseResource extends OkHttpResponseBodyResource implements HttpMessage {
15 |
16 | private final Response response;
17 |
18 | public OkHttpResponseResource(Response response) {
19 | super(response.body());
20 | this.response = response;
21 | }
22 |
23 | @Override
24 | @NonNull
25 | public String getDescription() {
26 | return "OkHttpResponse [ " + response + " ]";
27 | }
28 |
29 | @Override
30 | @Nullable
31 | public String getFilename() {
32 |
33 | String contentDisposition = response.header("Content-Disposition");
34 | if (StringUtils.hasText(contentDisposition)) {
35 | return ContentDisposition.parse(contentDisposition).getFilename();
36 | }
37 |
38 | return super.getFilename();
39 | }
40 |
41 | @Override
42 | @NonNull
43 | public URL getURL() {
44 | return response.request().url().url();
45 | }
46 |
47 | @Override
48 | @NonNull
49 | public URI getURI() {
50 | return response.request().url().uri();
51 | }
52 |
53 | @Override
54 | @NonNull
55 | public HttpHeaders getHeaders() {
56 | return OkHttpUtils.toSpringHeaders(response.headers());
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | secring.gpg
3 |
4 |
5 | # Created by https://www.gitignore.io/api/intellij,gradle
6 |
7 | ### Intellij ###
8 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
9 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
10 |
11 | # User-specific stuff:
12 | .idea/workspace.xml
13 | .idea/tasks.xml
14 |
15 | # Sensitive or high-churn files:
16 | .idea/dataSources/
17 | .idea/dataSources.ids
18 | .idea/dataSources.xml
19 | .idea/dataSources.local.xml
20 | .idea/sqlDataSources.xml
21 | .idea/dynamic.xml
22 | .idea/uiDesigner.xml
23 |
24 | # Gradle:
25 | .idea/gradle.xml
26 | .idea/libraries
27 |
28 | # Mongo Explorer plugin:
29 | .idea/mongoSettings.xml
30 |
31 | ## File-based project format:
32 | *.iws
33 |
34 | ## Plugin-specific files:
35 |
36 | # IntelliJ
37 | /out/
38 |
39 | # mpeltonen/sbt-idea plugin
40 | .idea_modules/
41 |
42 | # JIRA plugin
43 | atlassian-ide-plugin.xml
44 |
45 | # Crashlytics plugin (for Android Studio and IntelliJ)
46 | com_crashlytics_export_strings.xml
47 | crashlytics.properties
48 | crashlytics-build.properties
49 | fabric.properties
50 |
51 | ### Intellij Patch ###
52 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
53 |
54 | # *.iml
55 | # modules.xml
56 | # .idea/misc.xml
57 | # *.ipr
58 |
59 |
60 | ### Gradle ###
61 | .gradle
62 | build/
63 |
64 | # Ignore Gradle GUI config
65 | gradle-app.setting
66 |
67 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
68 | !gradle-wrapper.jar
69 |
70 | # Cache of project
71 | .gradletasknamecache
72 |
73 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
74 | # gradle/wrapper/gradle-wrapper.properties
75 |
76 | # End of https://www.gitignore.io/api/intellij,gradle
77 |
--------------------------------------------------------------------------------
/okhttp-spring-client/src/main/java/io/freefair/spring/okhttp/client/OkHttpClientResponse.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.client;
2 |
3 | import io.freefair.spring.okhttp.OkHttpUtils;
4 | import lombok.RequiredArgsConstructor;
5 | import okhttp3.Response;
6 | import okhttp3.ResponseBody;
7 | import org.springframework.http.HttpHeaders;
8 | import org.springframework.http.HttpStatusCode;
9 | import org.springframework.http.client.ClientHttpResponse;
10 |
11 | import java.io.InputStream;
12 |
13 | /**
14 | * OkHttp based {@link ClientHttpResponse} implementation.
15 | *
16 | * @author Lars Grefer
17 | * @see OkHttpClientRequest
18 | */
19 | @RequiredArgsConstructor
20 | public class OkHttpClientResponse implements ClientHttpResponse {
21 |
22 | private final Response okHttpResponse;
23 |
24 | private HttpHeaders springHeaders;
25 |
26 | @Override
27 | public HttpStatusCode getStatusCode() {
28 | return HttpStatusCode.valueOf(okHttpResponse.code());
29 | }
30 |
31 | @Override
32 | public String getStatusText() {
33 | return okHttpResponse.message();
34 | }
35 |
36 | @Override
37 | public void close() {
38 | ResponseBody body = okHttpResponse.body();
39 | if (body != null) {
40 | body.close();
41 | }
42 | }
43 |
44 | @Override
45 | public InputStream getBody() {
46 | ResponseBody body = okHttpResponse.body();
47 | if (body != null) {
48 | return body.byteStream();
49 | } else {
50 | return InputStream.nullInputStream();
51 | }
52 | }
53 |
54 | @Override
55 | public HttpHeaders getHeaders() {
56 | if (springHeaders == null) {
57 | springHeaders = OkHttpUtils.toSpringHeaders(okHttpResponse.headers());
58 | }
59 |
60 | return springHeaders;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/main/java/io/freefair/spring/okhttp/autoconfigure/logging/OkHttp3LoggingInterceptorAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.autoconfigure.logging;
2 |
3 | import io.freefair.spring.okhttp.ApplicationInterceptor;
4 | import io.freefair.spring.okhttp.autoconfigure.OkHttp3AutoConfiguration;
5 | import okhttp3.logging.HttpLoggingInterceptor;
6 | import org.springframework.beans.factory.ObjectProvider;
7 | import org.springframework.boot.autoconfigure.AutoConfiguration;
8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
11 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
12 | import org.springframework.context.annotation.Bean;
13 |
14 | /**
15 | * @author Lars Grefer
16 | */
17 | @AutoConfiguration(before = OkHttp3AutoConfiguration.class)
18 | @ConditionalOnClass(HttpLoggingInterceptor.class)
19 | @EnableConfigurationProperties(OkHttp3LoggingInterceptorProperties.class)
20 | public class OkHttp3LoggingInterceptorAutoConfiguration {
21 |
22 | @Bean
23 | @ApplicationInterceptor
24 | @ConditionalOnMissingBean
25 | @ConditionalOnProperty(value = "okhttp.logging.enabled", havingValue = "true", matchIfMissing = true)
26 | public HttpLoggingInterceptor okHttp3LoggingInterceptor(
27 | OkHttp3LoggingInterceptorProperties properties,
28 | ObjectProvider logger
29 | ) {
30 | HttpLoggingInterceptor.Logger actualLogger = logger.getIfUnique(() -> HttpLoggingInterceptor.Logger.DEFAULT);
31 |
32 | HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(actualLogger);
33 |
34 | httpLoggingInterceptor.setLevel(properties.getLevel());
35 |
36 | return httpLoggingInterceptor;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/okhttp-spring-client/src/test/java/io/freefair/spring/okhttp/OkHttpResponseResourceTest.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import io.freefair.spring.okhttp.OkHttpResponseResource;
4 | import okhttp3.*;
5 | import org.junit.jupiter.api.BeforeEach;
6 | import org.junit.jupiter.api.Test;
7 | import org.springframework.boot.test.context.SpringBootTest;
8 | import org.springframework.boot.test.web.server.LocalServerPort;
9 | import org.springframework.core.io.Resource;
10 | import org.springframework.http.ContentDisposition;
11 |
12 | import java.io.IOException;
13 | import java.nio.charset.StandardCharsets;
14 |
15 | import static org.assertj.core.api.Assertions.assertThat;
16 |
17 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
18 | class OkHttpResponseResourceTest {
19 |
20 | @LocalServerPort
21 | private int port;
22 |
23 | private OkHttpClient client;
24 |
25 | @BeforeEach
26 | public void setUp() {
27 | client = new OkHttpClient.Builder().build();
28 | }
29 |
30 | @Test
31 | void testResource() throws IOException {
32 |
33 | Request request = new Request.Builder().url("http://localhost:" + port + "/echo")
34 | .post(RequestBody.create("Hello World", MediaType.parse("text/plain")))
35 | .addHeader("Content-Disposition", ContentDisposition.attachment().filename("foo.txt").build().toString())
36 | .build();
37 |
38 | try (Response execute = client.newCall(request).execute()){
39 |
40 | Resource resource = new OkHttpResponseResource(execute);
41 |
42 | assertThat(resource.isFile()).isFalse();
43 | assertThat(resource.exists()).isTrue();
44 | assertThat(resource.getFilename()).isEqualTo("foo.txt");
45 | assertThat(resource.contentLength()).isEqualTo(11);
46 | assertThat(resource.getContentAsString(StandardCharsets.UTF_8)).isEqualTo("Hello World");
47 |
48 | }
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/main/java/io/freefair/spring/okhttp/autoconfigure/metrics/OkHttpMetricsProperties.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.autoconfigure.metrics;
2 |
3 | import io.micrometer.core.instrument.binder.okhttp3.OkHttpConnectionPoolMetrics;
4 | import io.micrometer.core.instrument.binder.okhttp3.OkHttpMetricsEventListener;
5 | import lombok.Data;
6 | import okhttp3.Request;
7 | import org.springframework.boot.context.properties.ConfigurationProperties;
8 | import org.springframework.boot.context.properties.NestedConfigurationProperty;
9 |
10 | import java.util.ArrayList;
11 | import java.util.HashMap;
12 | import java.util.List;
13 | import java.util.Map;
14 |
15 | /**
16 | * @author Lars Grefer
17 | * @see OkHttpMetricsEventListener
18 | * @see OkHttpMetricsAutoConfiguration
19 | */
20 | @Data
21 | @ConfigurationProperties("okhttp.metrics")
22 | public class OkHttpMetricsProperties {
23 |
24 | private boolean enabled = true;
25 |
26 | /**
27 | * Name for the metrics.
28 | */
29 | private String name = "okhttp";
30 |
31 | /**
32 | * Whether to include the {@code host} tag.
33 | *
34 | * @see OkHttpMetricsEventListener.Builder#includeHostTag(boolean)
35 | */
36 | private boolean includeHostTag = true;
37 |
38 | /**
39 | * Tag keys for {@link Request#tag()} or {@link Request#tag(Class)}.
40 | *
41 | * @see OkHttpMetricsEventListener.Builder#requestTagKeys(Iterable)
42 | */
43 | private List requestTagKeys = new ArrayList<>();
44 |
45 | /**
46 | * @see OkHttpMetricsEventListener.Builder#tags(Iterable)
47 | */
48 | private Map tags = new HashMap<>();
49 |
50 | @NestedConfigurationProperty
51 | private final ConnectionPoolMetricsProperties pool = new ConnectionPoolMetricsProperties();
52 |
53 | /**
54 | * @see OkHttpConnectionPoolMetrics
55 | */
56 | @Data
57 | public static class ConnectionPoolMetricsProperties {
58 |
59 | private boolean enabled = true;
60 |
61 | /**
62 | * @see OkHttpConnectionPoolMetrics#namePrefix
63 | */
64 | private String namePrefix = "okhttp.pool";
65 |
66 | /**
67 | * @see OkHttpConnectionPoolMetrics#tags
68 | */
69 | private Map tags = new HashMap<>();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/test/java/io/freefair/spring/okhttp/client/OkHttpClientRequestFactoryTest.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.client;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.SpringBootConfiguration;
7 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
8 | import org.springframework.boot.test.context.SpringBootTest;
9 | import org.springframework.boot.test.web.server.LocalServerPort;
10 | import org.springframework.boot.web.client.RestTemplateBuilder;
11 | import org.springframework.http.HttpHeaders;
12 | import org.springframework.web.bind.annotation.*;
13 | import org.springframework.web.client.RestTemplate;
14 |
15 | import static org.assertj.core.api.Assertions.assertThat;
16 |
17 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
18 | class OkHttpClientRequestFactoryTest {
19 |
20 | RestTemplate restTemplate;
21 |
22 | @Autowired
23 | RestTemplateBuilder restTemplateBuilder;
24 |
25 | @LocalServerPort
26 | private int port;
27 |
28 | @BeforeEach
29 | void setUp() {
30 | restTemplate = restTemplateBuilder
31 | .rootUri("http://localhost:" + port)
32 | .build();
33 | }
34 |
35 | @Test
36 | void get() {
37 | String response = restTemplate.getForObject("/user-agent", String.class);
38 |
39 | assertThat(response).contains("okhttp");
40 | }
41 |
42 | @Test
43 | void put() {
44 | restTemplate.put("/put", "foo");
45 | }
46 |
47 | @Test
48 | void post() {
49 | String response = restTemplate.postForObject("/post", "foobar", String.class);
50 |
51 | assertThat(response).contains("foobar");
52 | }
53 |
54 | @SpringBootConfiguration
55 | @EnableAutoConfiguration
56 | @RestController
57 | static class Config {
58 |
59 | @GetMapping("/user-agent")
60 | public String getUserAgent(@RequestHeader(HttpHeaders.USER_AGENT) String userAgent) {
61 | return userAgent;
62 | }
63 |
64 | @PutMapping("/put")
65 | public String put(@RequestBody String body) {
66 | return body;
67 | }
68 |
69 | @PostMapping("/post")
70 | public String post(@RequestBody String body) {
71 | return body;
72 | }
73 |
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/okhttp-spring-client/src/main/java/io/freefair/spring/okhttp/ResourceRequestBody.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import okhttp3.MediaType;
4 | import okhttp3.RequestBody;
5 | import okio.BufferedSink;
6 | import okio.Okio;
7 | import org.springframework.core.io.ByteArrayResource;
8 | import org.springframework.core.io.FileSystemResource;
9 | import org.springframework.core.io.InputStreamResource;
10 | import org.springframework.core.io.Resource;
11 | import org.springframework.lang.Nullable;
12 | import org.springframework.util.MimeType;
13 |
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 |
17 | /**
18 | * @author Lars Grefer
19 | */
20 | public class ResourceRequestBody extends RequestBody {
21 |
22 | private final Resource resource;
23 |
24 | @Nullable
25 | private final MediaType mediaType;
26 |
27 | public ResourceRequestBody(Resource resource) {
28 | this.resource = resource;
29 | this.mediaType = null;
30 | }
31 |
32 | public ResourceRequestBody(Resource resource, MimeType springMimeType) {
33 | this.resource = resource;
34 | this.mediaType = MediaType.parse(springMimeType.toString());
35 | }
36 |
37 | public ResourceRequestBody(Resource resource, MediaType okhttpMediaType) {
38 | this.resource = resource;
39 | this.mediaType = okhttpMediaType;
40 | }
41 |
42 | @Override
43 | @Nullable
44 | public MediaType contentType() {
45 | return mediaType;
46 | }
47 |
48 | @Override
49 | public void writeTo(BufferedSink bufferedSink) throws IOException {
50 | try (InputStream inputStream = resource.getInputStream()) {
51 | bufferedSink.writeAll(Okio.source(inputStream));
52 | }
53 | }
54 |
55 | @Override
56 | public long contentLength() throws IOException {
57 | if (resource instanceof InputStreamResource && resource.getClass().equals(InputStreamResource.class)) {
58 | return -1;
59 | }
60 | return resource.contentLength();
61 | }
62 |
63 | @Override
64 | public boolean isOneShot() {
65 | if (resource instanceof InputStreamResource) {
66 | return true;
67 | }
68 |
69 | if (resource instanceof ByteArrayResource) {
70 | return false;
71 | }
72 |
73 | if (resource instanceof FileSystemResource) {
74 | return false;
75 | }
76 |
77 | return super.isOneShot();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/okhttp-spring-client/src/main/java/io/freefair/spring/okhttp/OkHttpResponseBodyResource.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import lombok.Getter;
4 | import lombok.RequiredArgsConstructor;
5 | import okhttp3.ResponseBody;
6 | import org.springframework.core.io.AbstractResource;
7 | import org.springframework.lang.Nullable;
8 |
9 | import java.io.Closeable;
10 | import java.io.FileNotFoundException;
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.nio.charset.Charset;
14 |
15 | @RequiredArgsConstructor
16 | public class OkHttpResponseBodyResource extends AbstractResource implements Closeable {
17 |
18 | @Nullable
19 | @Getter
20 | private final ResponseBody responseBody;
21 | private boolean closed = false;
22 |
23 | @Override
24 | public byte[] getContentAsByteArray() throws IOException {
25 | if (responseBody == null) {
26 | throw new FileNotFoundException("Response body is null");
27 | }
28 | return responseBody.bytes();
29 | }
30 |
31 | @Override
32 | public String getContentAsString(Charset charset) throws IOException {
33 | if (responseBody == null) {
34 | throw new FileNotFoundException("Response body is null");
35 | }
36 | return responseBody.string();
37 | }
38 |
39 | @Override
40 | public String getDescription() {
41 | return "OkHttp ResponseBody " + responseBody;
42 | }
43 |
44 | @Override
45 | public InputStream getInputStream() throws FileNotFoundException {
46 | if (responseBody == null) {
47 | throw new FileNotFoundException("Response body is null");
48 | }
49 | return responseBody.byteStream();
50 | }
51 |
52 | @Override
53 | public long contentLength() throws FileNotFoundException {
54 | if (responseBody == null) {
55 | throw new FileNotFoundException("Response body is null");
56 | }
57 | return responseBody.contentLength();
58 | }
59 |
60 | @Override
61 | public boolean exists() {
62 | return responseBody != null;
63 | }
64 |
65 | @Override
66 | public boolean isOpen() {
67 | return exists() && !closed;
68 | }
69 |
70 | @Override
71 | public boolean isReadable() {
72 | return isOpen();
73 | }
74 |
75 | @Override
76 | public void close() {
77 | if (responseBody != null) {
78 | responseBody.close();
79 | }
80 | closed = true;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/test/java/io/freefair/spring/okhttp/ssl/SslBundleTest.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.ssl;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 | import org.springframework.boot.ssl.*;
7 | import org.springframework.boot.test.context.SpringBootTest;
8 | import org.springframework.boot.test.web.server.LocalServerPort;
9 | import org.springframework.boot.web.client.RestTemplateBuilder;
10 | import org.springframework.context.annotation.Bean;
11 | import org.springframework.context.annotation.Configuration;
12 | import org.springframework.web.bind.annotation.GetMapping;
13 | import org.springframework.web.bind.annotation.RestController;
14 | import org.springframework.web.client.RestTemplate;
15 |
16 | import javax.net.ssl.*;
17 |
18 | import static org.assertj.core.api.Assertions.assertThat;
19 |
20 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
21 | "server.ssl.enabled=true",
22 | "server.ssl.key-store=classpath:keystore.jks",
23 | "server.ssl.key-store-password=changeit",
24 | "server.ssl.key-alias=server-alias",
25 | "server.ssl.key-password=changeit",
26 | "spring.ssl.bundle.jks.a.truststore.location=classpath:keystore.jks",
27 | "spring.ssl.bundle.jks.a.truststore.password=changeit"
28 | })
29 | public class SslBundleTest {
30 |
31 | @Autowired
32 | private RestTemplateBuilder restTemplateBuilder;
33 |
34 | @Autowired
35 | private SslBundles sslBundles;
36 |
37 | @LocalServerPort
38 | private int port;
39 |
40 | @Test
41 | void testSsl() {
42 |
43 | SslBundle ssl = sslBundles.getBundle("a");
44 |
45 | RestTemplate restTemplate = restTemplateBuilder
46 | .sslBundle(ssl)
47 | .rootUri("https://localhost:" + port)
48 | .build();
49 |
50 | String result = restTemplate.getForObject("/foo", String.class);
51 | assertThat(result).isEqualTo("bar");
52 |
53 | }
54 |
55 | @Configuration
56 | @SpringBootApplication
57 | @RestController
58 | public static class TestController {
59 |
60 |
61 | @GetMapping("/foo")
62 | public String foo() {
63 | return "bar";
64 | }
65 |
66 | @Bean
67 | public HostnameVerifier hostnameVerifier() {
68 | return (hostname, session) -> true;
69 | }
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/test/java/io/freefair/spring/okhttp/OkHttp3AutoConfigurationIT.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import okhttp3.Interceptor;
4 | import okhttp3.OkHttpClient;
5 | import okhttp3.Response;
6 | import org.junit.jupiter.api.Test;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
9 | import org.springframework.boot.test.context.SpringBootTest;
10 | import org.springframework.context.annotation.Bean;
11 | import org.springframework.context.annotation.Configuration;
12 |
13 | import java.io.IOException;
14 |
15 | import static org.assertj.core.api.Assertions.assertThat;
16 | import static org.assertj.core.api.Assertions.fail;
17 |
18 | /**
19 | * @author Lars Grefer
20 | */
21 | @SpringBootTest
22 | public class OkHttp3AutoConfigurationIT {
23 |
24 | @Autowired
25 | private OkHttpClient okHttpClient;
26 |
27 | @Test
28 | public void testContextLoads() {
29 | assertThat(okHttpClient).isNotNull();
30 | }
31 |
32 | @Test
33 | public void testApplicationInterceptor() {
34 | assertThat(okHttpClient.interceptors()).size().isGreaterThan(0);
35 |
36 | for (Interceptor interceptor : okHttpClient.interceptors()) {
37 | if (interceptor instanceof MyInterceptor) {
38 | return;
39 | }
40 | }
41 | fail("MyInterceptor not found");
42 | }
43 |
44 | @Test
45 | public void testNetworkInterceptor() {
46 | assertThat(okHttpClient.networkInterceptors()).size().isGreaterThan(0);
47 |
48 | for (Interceptor interceptor : okHttpClient.networkInterceptors()) {
49 | if (interceptor instanceof MyInterceptor) {
50 | return;
51 | }
52 | }
53 | fail("MyInterceptor not found");
54 | }
55 |
56 | @Configuration
57 | @EnableAutoConfiguration
58 | static class TestConfig {
59 |
60 | @Bean
61 | @ApplicationInterceptor
62 | public Interceptor appInterceptor() {
63 | return new MyInterceptor();
64 | }
65 |
66 | @Bean
67 | @NetworkInterceptor
68 | public Interceptor netInterceptor() {
69 | return new MyInterceptor();
70 | }
71 | }
72 |
73 | static class MyInterceptor implements Interceptor {
74 |
75 | @Override
76 | public Response intercept(Chain chain) throws IOException {
77 | return chain.proceed(chain.request());
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/test/java/io/freefair/spring/okhttp/OkHttpRestTemplateAutoConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import io.freefair.spring.okhttp.client.OkHttpClientRequestFactory;
4 | import okhttp3.OkHttpClient;
5 | import org.junit.jupiter.api.Test;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.boot.SpringBootConfiguration;
8 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
9 | import org.springframework.boot.test.context.SpringBootTest;
10 | import org.springframework.boot.web.client.RestTemplateBuilder;
11 | import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
12 | import org.springframework.http.client.ClientHttpRequestFactory;
13 | import org.springframework.web.client.RestTemplate;
14 |
15 | import java.time.Duration;
16 |
17 | import static org.assertj.core.api.Assertions.assertThat;
18 |
19 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
20 | properties = {
21 | "okhttp.read-timeout=21s",
22 | "okhttp.connect-timeout=21s",
23 | "okhttp.write-timeout=21s"
24 | })
25 | class OkHttpRestTemplateAutoConfigurationTest {
26 |
27 | @Autowired
28 | private RestTemplateBuilder restTemplateBuilder;
29 |
30 | @Test
31 | void testTimeouts() throws NoSuchFieldException, IllegalAccessException {
32 | RestTemplate restTemplate = restTemplateBuilder.connectTimeout(Duration.ofSeconds(42)).build();
33 |
34 | OkHttpClient client = extractClient(restTemplate);
35 |
36 | assertThat(client.connectTimeoutMillis()).isEqualTo(Duration.ofSeconds(42).toMillis());
37 | assertThat(client.readTimeoutMillis()).isEqualTo(Duration.ofSeconds(21).toMillis());
38 | assertThat(client.writeTimeoutMillis()).isEqualTo(Duration.ofSeconds(21).toMillis());
39 | }
40 |
41 | private OkHttpClient extractClient(RestTemplate restTemplate) {
42 | ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory();
43 |
44 | while (requestFactory instanceof AbstractClientHttpRequestFactoryWrapper) {
45 | requestFactory = ((AbstractClientHttpRequestFactoryWrapper) requestFactory).getDelegate();
46 | }
47 |
48 | assertThat(requestFactory).isInstanceOf(OkHttpClientRequestFactory.class);
49 |
50 | return ((OkHttpClientRequestFactory)requestFactory).okHttpClient();
51 | }
52 |
53 | @SpringBootConfiguration
54 | @EnableAutoConfiguration
55 | public static class TestConfiguration {
56 |
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/test/java/io/freefair/spring/okhttp/OkHttpRestClientAutoConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import io.freefair.spring.okhttp.client.OkHttpClientRequestFactory;
4 | import okhttp3.OkHttpClient;
5 | import org.junit.jupiter.api.Test;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.boot.SpringBootConfiguration;
8 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
9 | import org.springframework.boot.test.context.SpringBootTest;
10 | import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
11 | import org.springframework.http.client.ClientHttpRequestFactory;
12 | import org.springframework.web.client.RestClient;
13 |
14 | import java.lang.reflect.Field;
15 | import java.time.Duration;
16 |
17 | import static org.assertj.core.api.Assertions.assertThat;
18 |
19 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
20 | properties = {
21 | "okhttp.read-timeout=21s",
22 | "okhttp.connect-timeout=21s",
23 | "okhttp.write-timeout=21s"
24 | })
25 | class OkHttpRestClientAutoConfigurationTest {
26 |
27 | @Autowired
28 | private RestClient.Builder restClientBuilder;
29 |
30 | @Test
31 | void testTimeouts() throws NoSuchFieldException, IllegalAccessException {
32 | RestClient restTemplate = restClientBuilder.build();
33 |
34 | OkHttpClient client = extractClient(restTemplate);
35 |
36 | assertThat(client.connectTimeoutMillis()).isEqualTo(Duration.ofSeconds(21).toMillis());
37 | assertThat(client.readTimeoutMillis()).isEqualTo(Duration.ofSeconds(21).toMillis());
38 | assertThat(client.writeTimeoutMillis()).isEqualTo(Duration.ofSeconds(21).toMillis());
39 | }
40 |
41 | private OkHttpClient extractClient(RestClient restClient) throws NoSuchFieldException, IllegalAccessException {
42 |
43 | Field clientRequestFactoryField = restClient.getClass().getDeclaredField("clientRequestFactory");
44 | clientRequestFactoryField.setAccessible(true);
45 | ClientHttpRequestFactory requestFactory = (ClientHttpRequestFactory) clientRequestFactoryField.get(restClient);
46 |
47 | while (requestFactory instanceof AbstractClientHttpRequestFactoryWrapper) {
48 | requestFactory = ((AbstractClientHttpRequestFactoryWrapper) requestFactory).getDelegate();
49 | }
50 |
51 | assertThat(requestFactory).isInstanceOf(OkHttpClientRequestFactory.class);
52 |
53 | return ((OkHttpClientRequestFactory)requestFactory).okHttpClient();
54 | }
55 |
56 | @SpringBootConfiguration
57 | @EnableAutoConfiguration
58 | public static class TestConfiguration {
59 |
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/config/checkstyle/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 |
74 |
75 | @rem Execute Gradle
76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
77 |
78 | :end
79 | @rem End local scope for the variables with windows NT shell
80 | if %ERRORLEVEL% equ 0 goto mainEnd
81 |
82 | :fail
83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
84 | rem the _cmd.exe /c_ return code!
85 | set EXIT_CODE=%ERRORLEVEL%
86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
88 | exit /b %EXIT_CODE%
89 |
90 | :mainEnd
91 | if "%OS%"=="Windows_NT" endlocal
92 |
93 | :omega
94 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/test/java/io/freefair/spring/okhttp/logging/OkHttp3LoggingInterceptorAutoConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.logging;
2 |
3 | import io.freefair.spring.okhttp.autoconfigure.logging.OkHttp3LoggingInterceptorAutoConfiguration;
4 | import okhttp3.logging.HttpLoggingInterceptor;
5 | import org.junit.jupiter.api.BeforeEach;
6 | import org.junit.jupiter.api.Test;
7 | import org.springframework.boot.autoconfigure.AutoConfigurations;
8 | import org.springframework.boot.test.context.runner.ApplicationContextRunner;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | import static org.assertj.core.api.Assertions.assertThat;
13 |
14 | /**
15 | * @author Lars Grefer
16 | */
17 | public class OkHttp3LoggingInterceptorAutoConfigurationTest {
18 |
19 | private ApplicationContextRunner applicationContextRunner;
20 |
21 | @BeforeEach
22 | public void setUp() {
23 | applicationContextRunner = new ApplicationContextRunner()
24 | .withConfiguration(AutoConfigurations.of(OkHttp3LoggingInterceptorAutoConfiguration.class));
25 | }
26 |
27 | @Test
28 | public void testDefaultLogger() {
29 | applicationContextRunner
30 | .run(context -> {
31 | assertThat(context).hasSingleBean(HttpLoggingInterceptor.class);
32 |
33 | assertThat(context).getBean(HttpLoggingInterceptor.class)
34 | .hasFieldOrPropertyWithValue("logger", HttpLoggingInterceptor.Logger.DEFAULT);
35 | });
36 | }
37 |
38 | @Test
39 | public void testDefaultLogger_enabled() {
40 | applicationContextRunner
41 | .withPropertyValues("okhttp.logging.enabled=true")
42 | .run(context -> {
43 | assertThat(context).hasSingleBean(HttpLoggingInterceptor.class);
44 |
45 | assertThat(context).getBean(HttpLoggingInterceptor.class)
46 | .hasFieldOrPropertyWithValue("logger", HttpLoggingInterceptor.Logger.DEFAULT);
47 | });
48 | }
49 |
50 | @Test
51 | public void testDefaultLogger_disabled() {
52 | applicationContextRunner
53 | .withPropertyValues("okhttp.logging.enabled=false")
54 | .run(context -> {
55 | assertThat(context).doesNotHaveBean(HttpLoggingInterceptor.class);
56 | });
57 | }
58 |
59 | @Test
60 | public void testCustomLogger() {
61 |
62 | applicationContextRunner
63 | .withUserConfiguration(CustomLoggerConfiguration.class)
64 | .run(context -> {
65 | assertThat(context).hasSingleBean(HttpLoggingInterceptor.class);
66 |
67 | assertThat(context).getBean(HttpLoggingInterceptor.class)
68 | .hasFieldOrPropertyWithValue("logger", CustomLoggerConfiguration.LOGGER);
69 | });
70 | }
71 |
72 | @Configuration
73 | static class CustomLoggerConfiguration {
74 |
75 | public static final HttpLoggingInterceptor.Logger LOGGER = message -> { };
76 |
77 | @Bean
78 | public HttpLoggingInterceptor.Logger customLogger() {
79 | return LOGGER;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/main/java/io/freefair/spring/okhttp/autoconfigure/OkHttpProperties.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.autoconfigure;
2 |
3 | import lombok.Data;
4 | import okhttp3.Protocol;
5 | import org.springframework.boot.context.properties.ConfigurationProperties;
6 | import org.springframework.boot.context.properties.NestedConfigurationProperty;
7 | import org.springframework.util.unit.DataSize;
8 |
9 | import java.io.File;
10 | import java.time.Duration;
11 | import java.util.List;
12 |
13 | /**
14 | * @author Lars Grefer
15 | */
16 | @Data
17 | @ConfigurationProperties(prefix = "okhttp")
18 | public class OkHttpProperties {
19 |
20 | /**
21 | * The default connect timeout for new connections.
22 | */
23 | private Duration connectTimeout = Duration.ofSeconds(10);
24 |
25 | /**
26 | * The default read timeout for new connections.
27 | */
28 | private Duration readTimeout = Duration.ofSeconds(10);
29 |
30 | /**
31 | * The default write timeout for new connections.
32 | */
33 | private Duration writeTimeout = Duration.ofSeconds(10);
34 |
35 | /**
36 | * The interval between web socket pings initiated by this client. Use this to
37 | * automatically send web socket ping frames until either the web socket fails or it is closed.
38 | * This keeps the connection alive and may detect connectivity failures early. No timeouts are
39 | * enforced on the acknowledging pongs.
40 | *
41 | * The default value of 0 disables client-initiated pings.
42 | */
43 | private Duration pingInterval = Duration.ZERO;
44 |
45 | @NestedConfigurationProperty
46 | private CacheProperties cache = new CacheProperties();
47 |
48 | /**
49 | * Whether to follow redirects from HTTPS to HTTP and from HTTP to HTTPS.
50 | */
51 | private boolean followSslRedirects = true;
52 |
53 | /**
54 | * Whether to follow redirects.
55 | */
56 | private boolean followRedirects = true;
57 |
58 | /**
59 | * Whether to retry or not when a connectivity problem is encountered.
60 | */
61 | private boolean retryOnConnectionFailure = true;
62 |
63 | /**
64 | * Configure the protocols used by this client to communicate with remote servers.
65 | */
66 | private List protocols = null;
67 |
68 | @NestedConfigurationProperty
69 | private final ConnectionPoolProperties connectionPool = new ConnectionPoolProperties();
70 |
71 | /**
72 | * @author Lars Grefer
73 | * @see okhttp3.Cache
74 | */
75 | @Data
76 | public static class CacheProperties {
77 |
78 | private boolean enabled;
79 |
80 | /**
81 | * The maximum number of bytes this cache should use to store.
82 | */
83 | private DataSize maxSize = DataSize.ofMegabytes(10);
84 |
85 | /**
86 | * The path of the directory where the cache should be stored.
87 | */
88 | private File directory;
89 | }
90 |
91 | /**
92 | * @see okhttp3.ConnectionPool
93 | */
94 | @Data
95 | public static class ConnectionPoolProperties {
96 |
97 | /**
98 | * The maximum number of idle connections for each address.
99 | */
100 | private int maxIdleConnections = 5;
101 |
102 | private Duration keepAliveDuration = Duration.ofMinutes(5);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/main/java/io/freefair/spring/okhttp/autoconfigure/OkHttpClientRequestFactoryBuilder.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.autoconfigure;
2 |
3 | import io.freefair.spring.okhttp.client.OkHttpClientRequestFactory;
4 | import lombok.RequiredArgsConstructor;
5 | import okhttp3.ConnectionSpec;
6 | import okhttp3.OkHttpClient;
7 | import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
8 | import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
9 | import org.springframework.boot.ssl.SslBundle;
10 | import org.springframework.boot.ssl.SslOptions;
11 | import org.springframework.lang.Nullable;
12 | import org.springframework.util.Assert;
13 |
14 | import javax.net.ssl.SSLContext;
15 | import javax.net.ssl.SSLSocketFactory;
16 | import javax.net.ssl.TrustManager;
17 | import javax.net.ssl.X509TrustManager;
18 | import java.time.Duration;
19 | import java.util.List;
20 |
21 | /**
22 | * @author Lars Grefer
23 | */
24 | @RequiredArgsConstructor
25 | public class OkHttpClientRequestFactoryBuilder implements ClientHttpRequestFactoryBuilder {
26 |
27 | private final OkHttpClient okHttpClient;
28 |
29 | @Override
30 | public OkHttpClientRequestFactory build() {
31 | return this.build(null);
32 | }
33 |
34 | @Override
35 | public OkHttpClientRequestFactory build(@Nullable ClientHttpRequestFactorySettings settings) {
36 | OkHttpClient.Builder builder = okHttpClient.newBuilder();
37 | if (settings == null) {
38 | return new OkHttpClientRequestFactory(builder.build());
39 | }
40 |
41 | Duration connectTimeout = settings.connectTimeout();
42 | if (connectTimeout != null) {
43 | builder.connectTimeout(connectTimeout);
44 | }
45 |
46 | Duration readTimeout = settings.readTimeout();
47 | if (readTimeout != null) {
48 | builder.readTimeout(readTimeout);
49 | }
50 |
51 | SslBundle sslBundle = settings.sslBundle();
52 | if (sslBundle != null) {
53 |
54 | SslOptions sslOptions = sslBundle.getOptions();
55 | if (sslOptions.isSpecified()) {
56 | ConnectionSpec connectionSpec = new ConnectionSpec.Builder(true)
57 | .cipherSuites(sslOptions.getCiphers())
58 | .tlsVersions(sslOptions.getEnabledProtocols())
59 | .build();
60 |
61 | builder.connectionSpecs(List.of(connectionSpec));
62 | }
63 |
64 | SSLContext sslContext = sslBundle.createSslContext();
65 | SSLSocketFactory socketFactory = sslContext.getSocketFactory();
66 |
67 | TrustManager[] trustManagers = sslBundle.getManagers().getTrustManagers();
68 | Assert.state(trustManagers.length == 1,
69 | "Trust material must be provided in the SSL bundle for OkHttp3ClientHttpRequestFactory");
70 |
71 | builder.sslSocketFactory(socketFactory, (X509TrustManager) trustManagers[0]);
72 | }
73 |
74 | ClientHttpRequestFactorySettings.Redirects redirects = settings.redirects();
75 | if (redirects != null) {
76 | switch (redirects) {
77 | case FOLLOW_WHEN_POSSIBLE, FOLLOW -> {
78 | builder.followRedirects(true);
79 | builder.followSslRedirects(true);
80 | }
81 | case DONT_FOLLOW -> {
82 | builder.followRedirects(false);
83 | builder.followSslRedirects(true);
84 | }
85 | default -> {}
86 | }
87 | }
88 |
89 | return new OkHttpClientRequestFactory(builder.build());
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/main/java/io/freefair/spring/okhttp/autoconfigure/metrics/OkHttpMetricsAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.autoconfigure.metrics;
2 |
3 | import io.freefair.spring.okhttp.autoconfigure.OkHttpProperties;
4 | import io.micrometer.core.instrument.binder.okhttp3.OkHttpConnectionPoolMetrics;
5 | import io.micrometer.core.instrument.binder.okhttp3.OkHttpMetricsEventListener;
6 | import io.micrometer.core.instrument.MeterRegistry;
7 | import io.micrometer.core.instrument.Tag;
8 | import okhttp3.ConnectionPool;
9 | import okhttp3.OkHttpClient;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
12 | import org.springframework.boot.autoconfigure.AutoConfiguration;
13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
15 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
16 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
17 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
18 | import org.springframework.context.annotation.Bean;
19 | import org.springframework.util.CollectionUtils;
20 |
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.stream.Collectors;
24 |
25 | @AutoConfiguration(after = CompositeMeterRegistryAutoConfiguration.class)
26 | @ConditionalOnBean(MeterRegistry.class)
27 | @ConditionalOnClass({MeterRegistry.class, OkHttpMetricsEventListener.class, OkHttpClient.class})
28 | @EnableConfigurationProperties(OkHttpMetricsProperties.class)
29 | public class OkHttpMetricsAutoConfiguration {
30 |
31 | @Autowired
32 | private OkHttpMetricsProperties properties;
33 |
34 | @Bean
35 | @ConditionalOnProperty(value = "okhttp.metrics.enabled", matchIfMissing = true, havingValue = "true")
36 | @ConditionalOnMissingBean
37 | public OkHttpMetricsEventListener okHttpMetricsEventListener(MeterRegistry meterRegistry) {
38 | OkHttpMetricsEventListener.Builder builder = OkHttpMetricsEventListener
39 | .builder(meterRegistry, properties.getName())
40 | .includeHostTag(properties.isIncludeHostTag());
41 |
42 | List requestTagKeys = properties.getRequestTagKeys();
43 | if (!CollectionUtils.isEmpty(requestTagKeys)) {
44 | builder = builder.requestTagKeys(requestTagKeys);
45 | }
46 |
47 | Map tags = properties.getTags();
48 | if (!CollectionUtils.isEmpty(tags)) {
49 | builder = builder.tags(getTags(tags));
50 | }
51 |
52 | return builder.build();
53 | }
54 |
55 | @Bean
56 | @ConditionalOnProperty(value = "okhttp.metrics.pool.enabled", matchIfMissing = true, havingValue = "true")
57 | @ConditionalOnBean(ConnectionPool.class)
58 | @ConditionalOnMissingBean
59 | public OkHttpConnectionPoolMetrics okHttpConnectionPoolMetrics(ConnectionPool connectionPool, OkHttpProperties okHttpProperties) {
60 | OkHttpMetricsProperties.ConnectionPoolMetricsProperties poolProperties = properties.getPool();
61 | return new OkHttpConnectionPoolMetrics(connectionPool,
62 | poolProperties.getNamePrefix(),
63 | getTags(poolProperties.getTags()),
64 | okHttpProperties.getConnectionPool().getMaxIdleConnections()
65 | );
66 | }
67 |
68 | private List getTags(Map tags) {
69 | return tags.entrySet()
70 | .stream()
71 | .map(entry -> Tag.of(entry.getKey(), entry.getValue()))
72 | .collect(Collectors.toList());
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/okhttp-spring-client/src/main/java/io/freefair/spring/okhttp/client/OkHttpClientRequest.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.client;
2 |
3 | import lombok.RequiredArgsConstructor;
4 | import okhttp3.*;
5 | import okio.Buffer;
6 | import okio.ByteString;
7 | import org.springframework.http.HttpHeaders;
8 | import org.springframework.http.HttpMethod;
9 | import org.springframework.http.StreamingHttpOutputMessage;
10 | import org.springframework.http.client.AbstractClientHttpRequest;
11 | import org.springframework.http.client.ClientHttpRequest;
12 | import org.springframework.http.client.ClientHttpResponse;
13 | import org.springframework.lang.Nullable;
14 | import org.springframework.util.Assert;
15 | import org.springframework.util.StringUtils;
16 |
17 | import java.io.IOException;
18 | import java.io.OutputStream;
19 | import java.net.MalformedURLException;
20 | import java.net.URI;
21 |
22 | /**
23 | * OkHttp based {@link ClientHttpRequest} implementation.
24 | *
25 | * @author Lars Grefer
26 | * @see OkHttpClientRequestFactory
27 | */
28 | @RequiredArgsConstructor
29 | public class OkHttpClientRequest extends AbstractClientHttpRequest implements StreamingHttpOutputMessage {
30 |
31 | private final OkHttpClient okHttpClient;
32 |
33 | private final URI uri;
34 |
35 | private final HttpMethod method;
36 |
37 |
38 | @Nullable
39 | private Body streamingBody;
40 |
41 | @Nullable
42 | private Buffer bufferBody;
43 |
44 |
45 | @Override
46 | public HttpMethod getMethod() {
47 | return method;
48 | }
49 |
50 | @Override
51 | public URI getURI() {
52 | return uri;
53 | }
54 |
55 | @Override
56 | public void setBody(Body body) {
57 | Assert.notNull(body, "body must not be null");
58 | assertNotExecuted();
59 | Assert.state(bufferBody == null, "getBody has already been used.");
60 | this.streamingBody = body;
61 | }
62 |
63 | @Override
64 | protected OutputStream getBodyInternal(HttpHeaders headers) {
65 | Assert.state(this.streamingBody == null, "setBody has already been used.");
66 |
67 | if (bufferBody == null) {
68 | bufferBody = new Buffer();
69 | }
70 |
71 | return bufferBody.outputStream();
72 | }
73 |
74 | @Override
75 | protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
76 |
77 | Request okHttpRequest = buildRequest(headers);
78 |
79 | Response okHttpResponse = this.okHttpClient.newCall(okHttpRequest).execute();
80 |
81 | return new OkHttpClientResponse(okHttpResponse);
82 | }
83 |
84 | private Request buildRequest(HttpHeaders headers) throws MalformedURLException {
85 |
86 | Request.Builder builder = new Request.Builder();
87 |
88 | builder.url(uri.toURL());
89 |
90 | MediaType contentType = null;
91 |
92 | String contentTypeHeader = headers.getFirst(HttpHeaders.CONTENT_TYPE);
93 | if (StringUtils.hasText(contentTypeHeader)) {
94 | contentType = MediaType.parse(contentTypeHeader);
95 | }
96 |
97 | RequestBody body = null;
98 |
99 | if (bufferBody != null) {
100 | ByteString bodyData = bufferBody.readByteString();
101 | if (headers.getContentLength() < 0) {
102 | headers.setContentLength(bodyData.size());
103 | }
104 | body = RequestBody.create(bodyData, contentType);
105 | } else if (streamingBody != null) {
106 | body = new StreamingBodyRequestBody(streamingBody, contentType, headers.getContentLength());
107 | } else if (okhttp3.internal.http.HttpMethod.requiresRequestBody(method.name())) {
108 | body = RequestBody.create(new byte[0], contentType);
109 | }
110 |
111 | builder.method(getMethod().name(), body);
112 |
113 | headers.forEach((name, values) -> {
114 | for (String value : values) {
115 | builder.addHeader(name, value);
116 | }
117 | });
118 |
119 | return builder.build();
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/test/java/io/freefair/spring/okhttp/OkHttp3AutoConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp;
2 |
3 | import io.freefair.spring.okhttp.autoconfigure.OkHttp3AutoConfiguration;
4 | import okhttp3.Cache;
5 | import okhttp3.Dns;
6 | import okhttp3.OkHttpClient;
7 | import okhttp3.Protocol;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.Test;
10 | import org.springframework.boot.autoconfigure.AutoConfigurations;
11 | import org.springframework.boot.test.context.runner.ApplicationContextRunner;
12 | import org.springframework.context.annotation.Bean;
13 | import org.springframework.context.annotation.Configuration;
14 |
15 | import java.io.File;
16 | import java.util.List;
17 |
18 | import static org.assertj.core.api.Assertions.assertThat;
19 | import static org.mockito.Mockito.mock;
20 |
21 |
22 | /**
23 | * @author Lars Grefer
24 | */
25 | public class OkHttp3AutoConfigurationTest {
26 |
27 | private ApplicationContextRunner applicationContextRunner;
28 |
29 | @BeforeEach
30 | public void setUp() {
31 | applicationContextRunner = new ApplicationContextRunner()
32 | .withConfiguration(AutoConfigurations.of(OkHttp3AutoConfiguration.class));
33 | }
34 |
35 | @Test
36 | public void testDefaultClient() {
37 | applicationContextRunner.run(context -> {
38 | assertThat(context).hasSingleBean(OkHttpClient.class);
39 | assertThat(context.getBean(OkHttpClient.class).cache()).isNotNull();
40 | assertThat(context).doesNotHaveBean(Dns.class);
41 | });
42 | }
43 |
44 | @Test
45 | public void testDns() {
46 | applicationContextRunner
47 | .withUserConfiguration(DnsConfiguration.class)
48 | .run(context -> {
49 | assertThat(context).hasSingleBean(OkHttpClient.class);
50 | assertThat(context).hasSingleBean(Dns.class);
51 |
52 | assertThat(context.getBean(OkHttpClient.class).dns()).isNotNull();
53 | });
54 | }
55 |
56 | @Test
57 | public void testNoCache() {
58 | applicationContextRunner
59 | .withPropertyValues("okhttp.cache.enabled=false")
60 | .run(context -> {
61 | assertThat(context).hasSingleBean(OkHttpClient.class);
62 | assertThat(context).doesNotHaveBean(Cache.class);
63 |
64 | assertThat(context.getBean(OkHttpClient.class).cache()).isNull();
65 | });
66 | }
67 |
68 | @Test
69 | public void testCustomCache() {
70 | applicationContextRunner
71 | .withUserConfiguration(CustomCacheConfiguration.class)
72 | .run(context -> {
73 | assertThat(context).hasSingleBean(OkHttpClient.class);
74 |
75 | assertThat(context.getBean(OkHttpClient.class).cache()).isEqualTo(CustomCacheConfiguration.CACHE);
76 | });
77 | }
78 |
79 | @Test
80 | public void testProtocols() {
81 | applicationContextRunner
82 | .withPropertyValues("okhttp.protocols=H2_PRIOR_KNOWLEDGE")
83 | .run(context -> {
84 | assertThat(context).hasSingleBean(OkHttpClient.class);
85 |
86 | List protocols = context.getBean(OkHttpClient.class).protocols();
87 | assertThat(protocols).containsExactly(Protocol.H2_PRIOR_KNOWLEDGE);
88 | });
89 | }
90 |
91 | @Test
92 | public void testProtocols_invalid() {
93 | applicationContextRunner
94 | .withPropertyValues("okhttp.protocols=Foo")
95 | .run(context -> {
96 | assertThat(context).hasFailed();
97 | });
98 | }
99 |
100 | @Test
101 | public void testProtocols_empty() {
102 | applicationContextRunner
103 | .withPropertyValues("okhttp.protocols=")
104 | .run(context -> {
105 | assertThat(context).hasSingleBean(OkHttpClient.class);
106 |
107 | List protocols = context.getBean(OkHttpClient.class).protocols();
108 | assertThat(protocols).contains(Protocol.HTTP_1_1, Protocol.HTTP_2);
109 | });
110 | }
111 |
112 | @Configuration
113 | static class CustomCacheConfiguration {
114 |
115 | static final Cache CACHE = new Cache(new File(""), 1);
116 |
117 | @Bean
118 | public Cache okHttp3Cache() {
119 | return CACHE;
120 | }
121 | }
122 |
123 | @Configuration
124 | static class DnsConfiguration {
125 | @Bean
126 | public Dns dns() {
127 | return mock(Dns.class);
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/okhttp-spring-boot-autoconfigure/src/main/java/io/freefair/spring/okhttp/autoconfigure/OkHttp3AutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.freefair.spring.okhttp.autoconfigure;
2 |
3 | import io.freefair.spring.okhttp.ApplicationInterceptor;
4 | import io.freefair.spring.okhttp.NetworkInterceptor;
5 | import io.freefair.spring.okhttp.OkHttp3Configurer;
6 | import jakarta.annotation.PreDestroy;
7 | import lombok.extern.slf4j.Slf4j;
8 | import okhttp3.*;
9 | import org.springframework.beans.factory.ObjectProvider;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.boot.autoconfigure.AutoConfiguration;
12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
15 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
16 | import org.springframework.context.annotation.Bean;
17 | import org.springframework.util.CollectionUtils;
18 | import org.springframework.util.FileSystemUtils;
19 |
20 | import javax.net.ssl.HostnameVerifier;
21 | import java.io.File;
22 | import java.io.IOException;
23 | import java.nio.file.Files;
24 | import java.time.Duration;
25 | import java.util.concurrent.TimeUnit;
26 |
27 | /**
28 | * @author Lars Grefer
29 | */
30 | @Slf4j
31 | @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
32 | @AutoConfiguration
33 | @ConditionalOnClass(OkHttpClient.class)
34 | @EnableConfigurationProperties(OkHttpProperties.class)
35 | public class OkHttp3AutoConfiguration {
36 |
37 | @Autowired
38 | private OkHttpProperties okHttpProperties;
39 |
40 | @Autowired
41 | private ObjectProvider configurers;
42 |
43 | @Autowired
44 | @ApplicationInterceptor
45 | private ObjectProvider applicationInterceptors;
46 |
47 | @Autowired
48 | @NetworkInterceptor
49 | private ObjectProvider networkInterceptors;
50 |
51 | private File tempDirCache = null;
52 |
53 | @Bean
54 | @ConditionalOnMissingBean
55 | public OkHttpClient okHttp3Client(
56 | ObjectProvider cache,
57 | ObjectProvider cookieJar,
58 | ObjectProvider dns,
59 | ObjectProvider hostnameVerifier,
60 | ObjectProvider certificatePinner,
61 | ConnectionPool connectionPool,
62 | ObjectProvider eventListener
63 | ) {
64 | OkHttpClient.Builder builder = new OkHttpClient.Builder();
65 |
66 | cache.ifUnique(builder::cache);
67 |
68 | eventListener.ifUnique(builder::eventListener);
69 |
70 | builder.connectTimeout(okHttpProperties.getConnectTimeout());
71 | builder.readTimeout(okHttpProperties.getReadTimeout());
72 | builder.writeTimeout(okHttpProperties.getWriteTimeout());
73 | builder.pingInterval(okHttpProperties.getPingInterval());
74 |
75 | cookieJar.ifUnique(builder::cookieJar);
76 |
77 | dns.ifUnique(builder::dns);
78 |
79 | hostnameVerifier.ifUnique(builder::hostnameVerifier);
80 | certificatePinner.ifUnique(builder::certificatePinner);
81 |
82 | builder.connectionPool(connectionPool);
83 |
84 | builder.followRedirects(okHttpProperties.isFollowRedirects());
85 | builder.followSslRedirects(okHttpProperties.isFollowSslRedirects());
86 | builder.retryOnConnectionFailure(okHttpProperties.isRetryOnConnectionFailure());
87 |
88 | if (!CollectionUtils.isEmpty(okHttpProperties.getProtocols())) {
89 | builder.protocols(okHttpProperties.getProtocols());
90 | }
91 |
92 | applicationInterceptors.forEach(builder::addInterceptor);
93 |
94 | networkInterceptors.forEach(builder::addNetworkInterceptor);
95 |
96 | configurers.forEach(configurer -> configurer.configure(builder));
97 |
98 | return builder.build();
99 | }
100 |
101 | @Bean
102 | @ConditionalOnMissingBean
103 | public ConnectionPool okHttp3ConnectionPool() {
104 | int maxIdleConnections = okHttpProperties.getConnectionPool().getMaxIdleConnections();
105 | Duration keepAliveDuration = okHttpProperties.getConnectionPool().getKeepAliveDuration();
106 | return new ConnectionPool(maxIdleConnections, keepAliveDuration.toNanos(), TimeUnit.NANOSECONDS);
107 | }
108 |
109 | @Bean
110 | @ConditionalOnMissingBean
111 | @ConditionalOnProperty(value = "okhttp.cache.enabled", havingValue = "true", matchIfMissing = true)
112 | public Cache okHttp3Cache() throws IOException {
113 | File directory = okHttpProperties.getCache().getDirectory();
114 | if (directory == null) {
115 | tempDirCache = Files.createTempDirectory("okhttp-cache").toFile();
116 | directory = tempDirCache;
117 | }
118 | return new Cache(directory, okHttpProperties.getCache().getMaxSize().toBytes());
119 | }
120 |
121 | @PreDestroy
122 | public void deleteTempCache() {
123 | if (tempDirCache != null) {
124 | log.debug("Deleting the temporary OkHttp Cache at {}", tempDirCache.getAbsolutePath());
125 | try {
126 | FileSystemUtils.deleteRecursively(tempDirCache);
127 | } catch (Exception e) {
128 | log.warn("Failed to delete the temporary OkHttp Cache at {}", tempDirCache.getAbsolutePath());
129 | }
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 |
118 |
119 | # Determine the Java command to use to start the JVM.
120 | if [ -n "$JAVA_HOME" ] ; then
121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
122 | # IBM's JDK on AIX uses strange locations for the executables
123 | JAVACMD=$JAVA_HOME/jre/sh/java
124 | else
125 | JAVACMD=$JAVA_HOME/bin/java
126 | fi
127 | if [ ! -x "$JAVACMD" ] ; then
128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
129 |
130 | Please set the JAVA_HOME variable in your environment to match the
131 | location of your Java installation."
132 | fi
133 | else
134 | JAVACMD=java
135 | if ! command -v java >/dev/null 2>&1
136 | then
137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
138 |
139 | Please set the JAVA_HOME variable in your environment to match the
140 | location of your Java installation."
141 | fi
142 | fi
143 |
144 | # Increase the maximum file descriptors if we can.
145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
146 | case $MAX_FD in #(
147 | max*)
148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
149 | # shellcheck disable=SC2039,SC3045
150 | MAX_FD=$( ulimit -H -n ) ||
151 | warn "Could not query maximum file descriptor limit"
152 | esac
153 | case $MAX_FD in #(
154 | '' | soft) :;; #(
155 | *)
156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
157 | # shellcheck disable=SC2039,SC3045
158 | ulimit -n "$MAX_FD" ||
159 | warn "Could not set maximum file descriptor limit to $MAX_FD"
160 | esac
161 | fi
162 |
163 | # Collect all arguments for the java command, stacking in reverse order:
164 | # * args from the command line
165 | # * the main class name
166 | # * -classpath
167 | # * -D...appname settings
168 | # * --module-path (only if needed)
169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
170 |
171 | # For Cygwin or MSYS, switch paths to Windows format before running java
172 | if "$cygwin" || "$msys" ; then
173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
214 | "$@"
215 |
216 | # Stop when "xargs" is not available.
217 | if ! command -v xargs >/dev/null 2>&1
218 | then
219 | die "xargs is not available"
220 | fi
221 |
222 | # Use "xargs" to parse quoted args.
223 | #
224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
225 | #
226 | # In Bash we could simply go:
227 | #
228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
229 | # set -- "${ARGS[@]}" "$@"
230 | #
231 | # but POSIX shell has neither arrays nor command substitution, so instead we
232 | # post-process each arg (as a line of input to sed) to backslash-escape any
233 | # character that might be a shell metacharacter, then use eval to reverse
234 | # that process (while maintaining the separation between arguments), and wrap
235 | # the whole thing up as a single "set" statement.
236 | #
237 | # This will of course break if any of these variables contains a newline or
238 | # an unmatched quote.
239 | #
240 |
241 | eval "set -- $(
242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
243 | xargs -n1 |
244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
245 | tr '\n' ' '
246 | )" '"$@"'
247 |
248 | exec "$JAVACMD" "$@"
249 |
--------------------------------------------------------------------------------