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