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