001/**
002 * Copyright (c) 2025-2026, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.gnu.org/licenses/lgpl-3.0.txt
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package dev.tinyflow.core.util;
017
018import okhttp3.*;
019import okio.BufferedSink;
020import org.jetbrains.annotations.NotNull;
021import org.slf4j.Logger;
022import org.slf4j.LoggerFactory;
023
024import java.io.File;
025import java.io.IOException;
026import java.io.InputStream;
027import java.util.Map;
028
029public class OKHttpClientWrapper {
030    private static final Logger LOG = LoggerFactory.getLogger(OKHttpClientWrapper.class);
031    private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
032
033    private final OkHttpClient okHttpClient;
034
035    public OKHttpClientWrapper() {
036        this(OkHttpClientUtil.buildDefaultClient());
037    }
038
039    public OKHttpClientWrapper(OkHttpClient okHttpClient) {
040        this.okHttpClient = okHttpClient;
041    }
042
043    public String get(String url) {
044        return executeString(url, "GET", null, null);
045    }
046
047    public byte[] getBytes(String url) {
048        return executeBytes(url, "GET", null, null);
049    }
050
051    public String get(String url, Map<String, String> headers) {
052        return executeString(url, "GET", headers, null);
053    }
054
055    public String post(String url, Map<String, String> headers, String payload) {
056        return executeString(url, "POST", headers, payload);
057    }
058
059    public byte[] postBytes(String url, Map<String, String> headers, String payload) {
060        return executeBytes(url, "POST", headers, payload);
061    }
062
063    public String put(String url, Map<String, String> headers, String payload) {
064        return executeString(url, "PUT", headers, payload);
065    }
066
067    public String delete(String url, Map<String, String> headers, String payload) {
068        return executeString(url, "DELETE", headers, payload);
069    }
070
071    public String multipartString(String url, Map<String, String> headers, Map<String, Object> payload) {
072        try (Response response = multipart(url, headers, payload);
073             ResponseBody body = response.body()) {
074            if (body != null) {
075                return body.string();
076            }
077        } catch (Exception e) {
078            LOG.error(e.toString(), e);
079        }
080        return null;
081    }
082
083
084    public byte[] multipartBytes(String url, Map<String, String> headers, Map<String, Object> payload) {
085        try (Response response = multipart(url, headers, payload);
086             ResponseBody body = response.body()) {
087            if (body != null) {
088                return body.bytes();
089            }
090        } catch (Exception e) {
091            LOG.error(e.toString(), e);
092        }
093        return null;
094    }
095
096
097    public Response multipart(String url, Map<String, String> headers, Map<String, Object> payload) throws IOException {
098        Request.Builder builder = new Request.Builder()
099            .url(url);
100
101        if (headers != null && !headers.isEmpty()) {
102            headers.forEach(builder::addHeader);
103        }
104
105        MultipartBody.Builder mbBuilder = new MultipartBody.Builder()
106            .setType(MultipartBody.FORM);
107        payload.forEach((s, o) -> {
108            if (o instanceof File) {
109                File f = (File) o;
110                RequestBody body = RequestBody.create(f, MediaType.parse("application/octet-stream"));
111                mbBuilder.addFormDataPart(s, f.getName(), body);
112            } else if (o instanceof InputStream) {
113                RequestBody body = new InputStreamRequestBody(MediaType.parse("application/octet-stream"), (InputStream) o);
114                mbBuilder.addFormDataPart(s, s, body);
115            } else if (o instanceof byte[]) {
116                mbBuilder.addFormDataPart(s, s, RequestBody.create((byte[]) o));
117            } else {
118                mbBuilder.addFormDataPart(s, String.valueOf(o));
119            }
120        });
121
122        MultipartBody multipartBody = mbBuilder.build();
123        Request request = builder.post(multipartBody).build();
124        return okHttpClient.newCall(request).execute();
125    }
126
127
128    public String executeString(String url, String method, Map<String, String> headers, String payload) {
129        try (Response response = execute0(url, method, headers, payload);
130             ResponseBody body = response.body()) {
131            if (body != null) {
132                return body.string();
133            }
134        } catch (Exception e) {
135            LOG.error(e.toString(), e);
136        }
137        return null;
138    }
139
140
141    public byte[] executeBytes(String url, String method, Map<String, String> headers, String payload) {
142        try (Response response = execute0(url, method, headers, payload);
143             ResponseBody body = response.body()) {
144            if (body != null) {
145                return body.bytes();
146            }
147        } catch (Exception e) {
148            LOG.error(e.toString(), e);
149        }
150        return null;
151    }
152
153
154    private Response execute0(String url, String method, Map<String, String> headers, String payload) throws IOException {
155        Request.Builder builder = new Request.Builder()
156            .url(url);
157
158        if (headers != null && !headers.isEmpty()) {
159            headers.forEach(builder::addHeader);
160        }
161
162        Request request;
163        if ("GET".equalsIgnoreCase(method)) {
164            request = builder.method(method, null).build();
165        } else {
166            RequestBody body = RequestBody.create(payload, JSON_TYPE);
167            request = builder.method(method, body).build();
168        }
169
170        return okHttpClient.newCall(request).execute();
171    }
172
173
174    public static class InputStreamRequestBody extends RequestBody {
175        private final InputStream inputStream;
176        private final MediaType contentType;
177
178        public InputStreamRequestBody(MediaType contentType, InputStream inputStream) {
179            if (inputStream == null) throw new NullPointerException("inputStream == null");
180            this.contentType = contentType;
181            this.inputStream = inputStream;
182        }
183
184        @Override
185        public MediaType contentType() {
186            return contentType;
187        }
188
189        @Override
190        public long contentLength() throws IOException {
191            return inputStream.available() == 0 ? -1 : inputStream.available();
192        }
193
194        @Override
195        public void writeTo(@NotNull BufferedSink sink) throws IOException {
196            IOUtil.copy(inputStream, sink);
197        }
198    }
199}