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