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.node; 017 018import com.agentsflex.core.chain.Chain; 019import com.agentsflex.core.chain.DataType; 020import com.agentsflex.core.chain.Parameter; 021import com.agentsflex.core.chain.node.BaseNode; 022import com.agentsflex.core.llm.client.OkHttpClientUtil; 023import com.agentsflex.core.util.StringUtil; 024import com.alibaba.fastjson.JSON; 025import com.alibaba.fastjson.JSONObject; 026import okhttp3.MediaType; 027import okhttp3.MultipartBody; 028import okhttp3.OkHttpClient; 029import okhttp3.Request; 030import okhttp3.RequestBody; 031import okhttp3.Response; 032import okhttp3.ResponseBody; 033 034import java.io.IOException; 035import java.io.UnsupportedEncodingException; 036import java.net.URLEncoder; 037import java.util.HashMap; 038import java.util.List; 039import java.util.Map; 040 041public class HttpNode extends BaseNode { 042 043 private String url; 044 private String method; 045 046 private List<Parameter> headers; 047 private List<Parameter> urlParameters; 048 049 private String bodyType; 050 private List<Parameter> fromData; 051 private List<Parameter> fromUrlencoded; 052 private String bodyJson; 053 private String rawBody; 054 055 public static String mapToQueryString(Map<String, Object> map) { 056 if (map == null || map.isEmpty()) { 057 return ""; 058 } 059 060 StringBuilder stringBuilder = new StringBuilder(); 061 062 for (String key : map.keySet()) { 063 if (StringUtil.noText(key)) { 064 continue; 065 } 066 if (stringBuilder.length() > 0) { 067 stringBuilder.append("&"); 068 } 069 stringBuilder.append(key.trim()); 070 stringBuilder.append("="); 071 Object value = map.get(key); 072 stringBuilder.append(value == null ? "" : urlEncode(value.toString().trim())); 073 } 074 return stringBuilder.toString(); 075 } 076 077 public static String urlEncode(String string) { 078 try { 079 return URLEncoder.encode(string, "UTF-8"); 080 } catch (UnsupportedEncodingException e) { 081 throw new RuntimeException(e); 082 } 083 } 084 085 public String getUrl() { 086 return url; 087 } 088 089 public void setUrl(String url) { 090 this.url = url; 091 } 092 093 public String getMethod() { 094 return method; 095 } 096 097 public void setMethod(String method) { 098 this.method = method; 099 } 100 101 public List<Parameter> getHeaders() { 102 return headers; 103 } 104 105 public void setHeaders(List<Parameter> headers) { 106 this.headers = headers; 107 } 108 109 public List<Parameter> getUrlParameters() { 110 return urlParameters; 111 } 112 113 public void setUrlParameters(List<Parameter> urlParameters) { 114 this.urlParameters = urlParameters; 115 } 116 117 public String getBodyType() { 118 return bodyType; 119 } 120 121 public void setBodyType(String bodyType) { 122 this.bodyType = bodyType; 123 } 124 125 public List<Parameter> getFromData() { 126 return fromData; 127 } 128 129 public void setFromData(List<Parameter> fromData) { 130 this.fromData = fromData; 131 } 132 133 public List<Parameter> getFromUrlencoded() { 134 return fromUrlencoded; 135 } 136 137 public void setFromUrlencoded(List<Parameter> fromUrlencoded) { 138 this.fromUrlencoded = fromUrlencoded; 139 } 140 141 public String getBodyJson() { 142 return bodyJson; 143 } 144 145 public void setBodyJson(String bodyJson) { 146 this.bodyJson = bodyJson; 147 } 148 149 public String getRawBody() { 150 return rawBody; 151 } 152 153 public void setRawBody(String rawBody) { 154 this.rawBody = rawBody; 155 } 156 157 @Override 158 protected Map<String, Object> execute(Chain chain) { 159 160 Map<String, Object> urlDataMap = chain.getParameterValues(this, urlParameters); 161 String parametersString = mapToQueryString(urlDataMap); 162 String newUrl = "POST".equalsIgnoreCase(method) || parametersString.isEmpty() ? url : url + 163 (url.contains("?") ? "&" + parametersString : "?" + parametersString); 164 165 Request.Builder reqBuilder = new Request.Builder().url(newUrl); 166 167 Map<String, Object> headersMap = chain.getParameterValues(this, headers); 168 headersMap.forEach((s, o) -> reqBuilder.addHeader(s, String.valueOf(o))); 169 170 if (StringUtil.noText(method) || "GET".equalsIgnoreCase(method)) { 171 reqBuilder.method("GET", null); 172 } else { 173 reqBuilder.method(method.toUpperCase(), getRequestBody(chain, urlDataMap)); 174 } 175 176 177 OkHttpClient okHttpClient = OkHttpClientUtil.buildDefaultClient(); 178 try (Response response = okHttpClient.newCall(reqBuilder.build()).execute()) { 179 180 Map<String, Object> result = new HashMap<>(); 181 result.put("statusCode", response.code()); 182 183 Map<String, String> responseHeaders = new HashMap<>(); 184 for (String name : response.headers().names()) { 185 responseHeaders.put(name, response.header(name)); 186 } 187 result.put("headers", responseHeaders); 188 189 ResponseBody body = response.body(); 190 String bodyString = null; 191 if (body != null) bodyString = body.string(); 192 193 if (StringUtil.hasText(bodyString)) { 194 DataType outDataType = null; 195 List<Parameter> outputDefs = getOutputDefs(); 196 if (outputDefs != null) { 197 for (Parameter outputDef : outputDefs) { 198 if ("body".equalsIgnoreCase(outputDef.getName())) { 199 outDataType = outputDef.getDataType(); 200 break; 201 } 202 } 203 } 204 205 if (outDataType != null && (outDataType == DataType.Object || outDataType 206 .getValue().startsWith("Array"))) { 207 result.put("body", JSON.parse(bodyString)); 208 } else { 209 result.put("body", bodyString); 210 } 211 } 212 return result; 213 } catch (IOException e) { 214 throw new RuntimeException(e); 215 } 216 } 217 218 private RequestBody getRequestBody(Chain chain, Map<String, Object> urlDataMap) { 219 if ("json".equals(bodyType)) { 220 JSONObject object = JSON.parseObject(bodyJson); 221 object.putAll(urlDataMap); 222 return RequestBody.create(JSON.toJSONString(object), MediaType.parse("application/json")); 223 } 224 225 if ("x-www-form-urlencoded".equals(bodyType)) { 226 Map<String, Object> formUrlencodedMap = chain.getParameterValues(this, fromUrlencoded); 227 String bodyString = mapToQueryString(formUrlencodedMap); 228 return RequestBody.create(bodyString, MediaType.parse("application/x-www-form-urlencoded")); 229 } 230 231 if ("form-data".equals(bodyType)) { 232 Map<String, Object> formDataMap = chain.getParameterValues(this, fromData); 233 234 MultipartBody.Builder builder = new MultipartBody.Builder() 235 .setType(MultipartBody.FORM); 236 237 formDataMap.forEach((s, o) -> { 238// if (o instanceof File) { 239// File f = (File) o; 240// RequestBody body = RequestBody.create(f, MediaType.parse("application/octet-stream")); 241// builder.addFormDataPart(s, f.getName(), body); 242// } else if (o instanceof InputStream) { 243// RequestBody body = new HttpClient.InputStreamRequestBody(MediaType.parse("application/octet-stream"), (InputStream) o); 244// builder.addFormDataPart(s, s, body); 245// } else if (o instanceof byte[]) { 246// builder.addFormDataPart(s, s, RequestBody.create((byte[]) o)); 247// } else { 248// builder.addFormDataPart(s, String.valueOf(o)); 249// } 250 builder.addFormDataPart(s, String.valueOf(o)); 251 }); 252 253 return builder.build(); 254 } 255 256 if ("raw".equals(bodyType)) { 257 return RequestBody.create(rawBody, null); 258 } 259 //none 260 return RequestBody.create("", null); 261 } 262 263 @Override 264 public String toString() { 265 return "HttpNode{" + 266 "url='" + url + '\'' + 267 ", method='" + method + '\'' + 268 ", headers=" + headers + 269 ", parameters=" + urlParameters + 270 ", bodyType='" + bodyType + '\'' + 271 ", fromData=" + fromData + 272 ", fromUrlencoded=" + fromUrlencoded + 273 ", bodyJson='" + bodyJson + '\'' + 274 ", rawBody='" + rawBody + '\'' + 275 ", description='" + description + '\'' + 276 ", parameter=" + urlParameters + 277 ", outputDefs=" + outputDefs + 278 ", id='" + id + '\'' + 279 ", name='" + name + '\'' + 280 ", async=" + async + 281 ", inwardEdges=" + inwardEdges + 282 ", outwardEdges=" + outwardEdges + 283 ", condition=" + condition + 284 ", memory=" + memory + 285 ", nodeStatus=" + nodeStatus + 286 '}'; 287 } 288}