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