All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alibaba.dashscope.protocol.ServiceFacility Maven / Gradle / Ivy

package com.alibaba.dashscope.protocol;

import static com.alibaba.dashscope.utils.ApiKeywords.*;
import static java.lang.Thread.sleep;

import com.alibaba.dashscope.common.*;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.protocol.pool.WebsocketPool;
import com.alibaba.dashscope.utils.Constants;
import com.alibaba.dashscope.utils.JsonUtils;
import com.google.common.collect.Maps;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Emitter;
import io.reactivex.Flowable;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import lombok.Builder;
import lombok.Data;
import lombok.var;
import org.apache.http.HttpStatus;

/** The rpc factory to use both http rpc and ws rpc. */
public class ServiceFacility {

  /**
   * Call the network to do async task submitting and queries. Only support http.
   *
   * @param extraHeaders Extra headers to pass, basic headers will be built inside this function.
   * @param param The input param to submit the task.
   * @param method The http method.
   * @param receiveTimeout The reception timeout of a single message.
   * @return The final result after waiting.
   */
  public static Result callAndWait(
      Map extraHeaders, Param param, HttpMethod method, long receiveTimeout) {
    var headers = prepareHeaders(Protocol.HTTP, param);
    if (extraHeaders != null) {
      headers.putAll(extraHeaders);
    }
    SubmitResult result =
        (SubmitResult)
            call(Protocol.HTTP, extraHeaders, param, method, receiveTimeout, SubmitResult.class);
    SubmitParam submitParam =
        SubmitParam.builder().taskId(result.getTaskId()).apiKey(param.getApiKey()).build();
    return wait(submitParam, param.resultType(), receiveTimeout);
  }

  /**
   * Wait for an async task to finish, check every 5 seconds. Only support http.
   *
   * @param submitParam The `SubmitParam` used to query the task.
   * @param clazz The result class.
   * @param receiveTimeout The reception timeout.
   * @return The task result.
   */
  public static Result wait(
      SubmitParam submitParam, Class clazz, long receiveTimeout) {
    while (true) {
      try {
        submitParam.setMode(StreamingMode.NONE);
        var headers = prepareHeaders(Protocol.HTTP, submitParam);
        String url = prepareUrl(Protocol.HTTP, submitParam);
        Response response =
            streamingNone(
                Protocol.HTTP,
                url,
                headers,
                prepareRequest(Protocol.HTTP, submitParam, WebSocketEventType.RUN_TASK),
                HttpMethod.GET,
                receiveTimeout);
        JsonObject jsonObject = JsonUtils.parse(response.getMessage());
        JsonObject output = jsonObject.getAsJsonObject(OUTPUT);
        String taskStatus =
            output.get(TASK_STATUS) == null ? null : output.get(TASK_STATUS).getAsString();
        if (TaskStatus.FAILED.getValue().equals(taskStatus)
            || TaskStatus.CANCELED.getValue().equals(taskStatus)
            || TaskStatus.SUCCEEDED.getValue().equals(taskStatus)
            || TaskStatus.UNKNOWN.getValue().equals(taskStatus)) {
          return prepareResult(Protocol.HTTP, response, clazz);
        }
      } catch (ApiException e) {
        if (e.getStatus().getStatusCode() != HttpStatus.SC_SERVICE_UNAVAILABLE
            && e.getStatus().getStatusCode() != HttpStatus.SC_GATEWAY_TIMEOUT) {
          throw e;
        }
      } finally {
        try {
          sleep(5000);
        } catch (InterruptedException ignored) {
        }
      }
    }
  }

  /**
   * Call the network to do a stream-in, stream-out request. Only support websocket. This function
   * will automatically construct the Request and the Result, and build url, headers, body, etc. The
   * websocket command start-task/continue-task/finish-task will also be built here, so user need
   * only to pass the payload and some necessary information into the params. If the payload is
   * binaries, the first message may be binary+parameters, or parameters only.
   *
   * 

You can use this function to send binary(binaries). * * @param extraHeaders Extra headers to pass, basic headers will be built inside this function. * @param params The input param containing the necessary payload information. * @param receiveTimeout The reception timeout of a single message. * @return The results in a Flowable */ public static Flowable streamCall( Map extraHeaders, Flowable params, long receiveTimeout) { var wsParam = prepareWsParams(params); Flowable requests = wsParam.getRequests(); Param param = wsParam.getFirstParam(); var headers = prepareHeaders(Protocol.WEBSOCKET, param); if (extraHeaders != null) { headers.putAll(extraHeaders); } return streamingInAndDuplex(Constants.baseWebsocketApiUrl, headers, requests, receiveTimeout) .map(response -> prepareResult(Protocol.WEBSOCKET, param, response)); } static class DuplexCallback extends RpcResponseCallback { public DuplexCallback( ResultCallback callback, Function converter, WebsocketRpc websocketRpc) { super(callback, converter, websocketRpc); } @Override public void onEvent(Response message) { if (isStartEvent(message.getMessage())) { return; } super.onEvent(message); } } /** * Call the network to do a single-in, stream-out request via a callback. Only support websocket. * This function will automatically construct the Request and the Result, and build url, headers, * body, etc. The websocket command start-task/continue-task/finish-task will also be built here, * so user need only to pass the payload and some necessary information into the params. The * callback is supposed to receive the Result and the error messages. * *

You can use this function to send binary(binaries). * * @param extraHeaders Extra headers to pass, basic headers will be built inside this function. * @param params The input param containing the necessary payload information. * @param resultCallback The callback to receive messages. */ public static void streamCall( Map extraHeaders, Flowable params, ResultCallback resultCallback) { var wsParam = prepareWsParams(params); Flowable requests = wsParam.getRequests(); Param param = wsParam.getFirstParam(); var headers = prepareHeaders(Protocol.WEBSOCKET, param); if (extraHeaders != null) { headers.putAll(extraHeaders); } streamingInAndDuplexWithCallback( Constants.baseWebsocketApiUrl, headers, requests, new DuplexCallback( resultCallback, input -> prepareResult(Protocol.WEBSOCKET, param, input), null)); } /** * Call the network to do a single-in, stream-out request. This function will automatically * construct the Request and the Result, and build url, headers, body, etc. The websocket command * start-task will also be built here, so user need only to pass the payload and some necessary * information into the Param. * *

This function does not support binary. * * @param protocol The protocol. * @param extraHeaders Extra headers to pass, basic headers will be built inside this function. * @param param The input param containing the necessary payload information. * @param receiveTimeout The reception timeout. * @return The iterable result. * @throws ApiException when error occurs. */ public static Flowable streamCall( Protocol protocol, Map extraHeaders, Param param, long receiveTimeout) { param.setMode(StreamingMode.OUT); var headers = prepareHeaders(protocol, param); if (extraHeaders != null) { headers.putAll(extraHeaders); } String url = prepareUrl(protocol, param); return streamingOut( protocol, url, headers, prepareRequest(protocol, param, WebSocketEventType.RUN_TASK), HttpMethod.POST, StreamingMode.OUT, receiveTimeout) .map(response -> prepareResult(protocol, param, response)); } /** * Call the network to do a single-in, single-out request. This function will automatically * construct the Request and the Result, and build url, headers, body, etc. The websocket command * start-task will also be built here, so user need only to pass the payload and some necessary * information into the Param. * *

This function does not support binary. * * @param protocol The protocol. * @param extraHeaders Extra headers to pass, basic headers will be built inside this function. * @param param The input param containing the necessary payload information. * @param method The http method. * @param receiveTimeout The reception timeout. * @return The result. * @throws ApiException when error occurs. */ public static Result call( Protocol protocol, Map extraHeaders, Param param, HttpMethod method, long receiveTimeout) throws ApiException { return call(protocol, extraHeaders, param, method, receiveTimeout, param.resultType()); } private static Result call( Protocol protocol, Map extraHeaders, Param param, HttpMethod method, long receiveTimeout, Class clazz) throws ApiException { param.setMode(StreamingMode.NONE); var headers = prepareHeaders(protocol, param); if (extraHeaders != null) { headers.putAll(extraHeaders); } try { String url = prepareUrl(protocol, param); Response response = streamingNone( protocol, url, headers, prepareRequest(protocol, param, WebSocketEventType.RUN_TASK), method, receiveTimeout); return prepareResult(protocol, response, clazz); } catch (Exception e) { throw e instanceof ApiException ? (ApiException) e : new ApiException(e); } } /** * Prepare the connecting url. * * @param protocol The protocol. * @param param The input param. * @return The constructed url. * @throws ApiException when the protocol is not supported. */ public static String prepareUrl(Protocol protocol, Param param) throws ApiException { switch (protocol) { case HTTP: String baseUrl = Constants.baseHttpApiUrl; if (baseUrl.endsWith("/")) { baseUrl = baseUrl.substring(0, baseUrl.length() - 1); } return baseUrl + param.url(); case WEBSOCKET: return Constants.baseWebsocketApiUrl; default: throw new ApiException( Status.builder() .statusCode(HttpStatus.SC_BAD_REQUEST) .code(ErrorType.PROTOCOL_UNSUPPORTED.getValue()) .message("Invalid protocol: " + protocol) .build()); } } /** * Prepare common headers. * * @param protocol The protocol. * @param param The input param. * @return Headers. */ public static Map prepareHeaders(Protocol protocol, Param param) { Map headers = Maps.newHashMap(); headers.put("Authorization", param.getApiKey()); if (param.isSecurityCheck()) { headers.put("X-DashScope-DataInspection", "enable"); } if (protocol == Protocol.HTTP) { headers.put("Authorization", "Bearer " + param.getApiKey()); headers.put("Content-Type", "application/json"); if (param.getMode() == StreamingMode.OUT) { headers.put("Accept", "text/event-stream"); headers.put("X-Accel-Buffering", "no"); headers.put("X-DashScope-SSE", "enable"); } else if (param.getMode() == StreamingMode.NONE) { headers.put("Accept", "application/json"); } } return headers; } /** * Build the message body by the protocol. * * @param protocol The protocol, can be 'http' or 'websocket'. * @param param The input param * @param eventType The WebSocketEventType to built into the Request * @return The message body * @throws ApiException if the input is invalid. */ public static Request prepareRequest( Protocol protocol, Param param, WebSocketEventType eventType) { Request request = param.toRequest(protocol); if (protocol == Protocol.WEBSOCKET) { if (eventType == WebSocketEventType.RUN_TASK || eventType == WebSocketEventType.FINISH_TASK || request.getMessage() != null) { WsHeader header = WsHeader.buildInputHeader(param.getRequestId(), param.getMode(), eventType); Map map = Maps.newHashMap(); map.put(HEADER, header); map.put( PAYLOAD, request.getMessage() == null ? new JsonObject() : JsonUtils.parse(request.getMessage())); request.setMessage(JsonUtils.toJson(map)); if (eventType == WebSocketEventType.FINISH_TASK) { request.setBinary(null); } } } return request; } @Data @Builder static class WsParam { private Flowable requests; private Param firstParam; } /** * Add start-task and finish-task to the input stream. * * @param params The params needed to send. * @return A class contains the constructed requests and the first param. */ public static WsParam prepareWsParams(Flowable params) { AtomicInteger integer = new AtomicInteger(0); var firstParam = params.blockingFirst(); var requests = params .flatMap( param -> { if (integer.getAndIncrement() == 0) { Request request = prepareRequest(Protocol.WEBSOCKET, param, WebSocketEventType.RUN_TASK); if (request.getBinary() != null) { return Flowable.fromArray( Request.builder().message(request.getMessage()).build(), Request.builder().binary(request.getBinary()).build()); } else { return Flowable.fromArray(request); } } else { return Flowable.fromArray( prepareRequest( Protocol.WEBSOCKET, param, WebSocketEventType.CONTINUE_TASK)); } }) .concatWith( Flowable.defer( () -> Flowable.just( prepareRequest( Protocol.WEBSOCKET, firstParam, WebSocketEventType.FINISH_TASK)))); return WsParam.builder().requests(requests).firstParam(firstParam).build(); } /** * Prepare the Result by the response and the input param. * * @param protocol The protocol. * @param param The input param to call `returnType` * @param res The response. * @return The constructed Result instance. */ public static Result prepareResult(Protocol protocol, Param param, Response res) { return prepareResult(protocol, res, param.resultType()); } private static Result prepareResult( Protocol protocol, Response res, Class clazz) { try { Result result = clazz.newInstance(); Response response = prepareResponsePayload(protocol, res); if (Protocol.WEBSOCKET == protocol) { result.setRequestId(response.getHeaders().get(TASKID)); } else { JsonObject jsonObject = JsonUtils.parse(response.getMessage()); result.setRequestId( jsonObject.get(REQUEST_ID) == null ? null : jsonObject.get(REQUEST_ID).getAsString()); } result.fromResponse(protocol, response); return result; } catch (Exception e) { throw new ApiException(e); } } /** * Call rpc on single-in, stream-out mode * * @param protocol The protocol. * @param url The url. * @param headers The headers. * @param request The input request. * @param receiveTimeout The timeout of receiving single message. * @param httpMethod The `HttpMethod` to use if the protocol is http. * @param mode The streaming mode. * @return The Flowable of `Response` */ public static Flowable streamingOut( Protocol protocol, String url, Map headers, Request request, HttpMethod httpMethod, StreamingMode mode, long receiveTimeout) throws ApiException { return Flowable.create( emitter -> ServiceFacility.doSend(emitter, protocol, url, headers, request, httpMethod, mode), BackpressureStrategy.BUFFER) .filter( message -> protocol == Protocol.HTTP || message.getMessage() == null || !isStartEvent(message.getMessage())) .timeout(receiveTimeout, TimeUnit.SECONDS); } /** * Call rpc on single-in, single-out mode * * @param protocol The protocol. * @param url The url. * @param headers The headers. * @param request The input request. * @param receiveTimeout The timeout of receiving single message. * @param httpMethod The `HttpMethod` to use if the protocol is http. * @return The Flowable of `Response` */ public static Response streamingNone( Protocol protocol, String url, Map headers, Request request, HttpMethod httpMethod, long receiveTimeout) throws ApiException { return streamingOut( protocol, url, headers, request, httpMethod, StreamingMode.NONE, receiveTimeout) .filter( message -> protocol == Protocol.HTTP || message.getMessage() == null || !isStartEvent(message.getMessage())) .blockingFirst(); } /** * Call rpc on single-in mode with a callback. * * @param protocol The protocol. * @param url The url. * @param headers The headers. * @param request The input request. * @param mode The streaming mode. * @param httpMethod The `HttpMethod` to use if the protocol is http. * @param callback The custom callback to use. */ public static void streamingOutWithCallback( Protocol protocol, String url, Map headers, Request request, HttpMethod httpMethod, StreamingMode mode, ResultCallback callback) { var e = checkApiKey(headers); if (e != null) { callback.onError(e); return; } switch (protocol) { case HTTP: HttpRpc httpRpc = new HttpRpc(url, new RpcResponseCallback<>(callback, input -> input, null)); switch (httpMethod) { case GET: httpRpc.get(headers); break; case POST: if (mode == StreamingMode.NONE) { httpRpc.post(headers, request); } else { httpRpc.sse(headers, request); } break; default: callback.onError( new ApiException( Status.builder() .statusCode(HttpStatus.SC_BAD_REQUEST) .code(ErrorType.PROTOCOL_UNSUPPORTED.getValue()) .message("Invalid Http Method: " + httpMethod) .build())); break; } break; case WEBSOCKET: try { WebsocketRpc websocketRpc = WebsocketPool.getInstance().getWsClient(url, headers); websocketRpc.call( request, new RpcResponseCallback<>(callback, input -> input, websocketRpc), ServiceFacility::isFinishEvent); } catch (Exception ex) { callback.onError(ex instanceof ApiException ? ex : new ApiException(ex)); } break; } } /** * Call rpc with a callback on stream-in mode, only support websocket. * * @param url The url. * @param headers The headers. * @param requests The iterable input request. * @param callback The custom callback to use. */ public static void streamingInAndDuplexWithCallback( String url, Map headers, Flowable requests, ResultCallback callback) { var e = checkApiKey(headers); if (e != null) { callback.onError(e); return; } try { WebsocketRpc websocketRpc = WebsocketPool.getInstance().getWsClient(url, headers); websocketRpc.call( requests, new RpcResponseCallback<>(callback, input -> input, websocketRpc), ServiceFacility::isFinishEvent); } catch (Exception ex) { callback.onError(ex instanceof ApiException ? ex : new ApiException(ex)); } } /** * Call rpc on stream-in mode, only support websocket. * * @param url The url. * @param headers The headers. * @param requests The iterable input request. * @param receiveTimeout The message timeout. * @return A Flowable of `Response` */ public static Flowable streamingInAndDuplex( String url, Map headers, Flowable requests, long receiveTimeout) { return Flowable.create( emitter -> { var e = checkApiKey(headers); if (e != null) { emitter.onError(e); return; } try { WebsocketRpc websocketRpc = WebsocketPool.getInstance().getWsClient(url, headers); websocketRpc.call( requests, new RpcResponseCallback<>(emitter, input -> input, websocketRpc), ServiceFacility::isFinishEvent); } catch (Exception ex) { emitter.onError(ex instanceof ApiException ? ex : new ApiException(ex)); } }, BackpressureStrategy.BUFFER) .filter(message -> message.getMessage() == null || !isStartEvent(message.getMessage())) .timeout(receiveTimeout, TimeUnit.SECONDS); } private static boolean isEvent(String msg, WebSocketEventType eventType) { if (msg == null) { return false; } JsonObject jsonObject = JsonUtils.parse(msg); JsonObject header = jsonObject.getAsJsonObject(HEADER); return header != null && eventType.getValue().equals(header.get(EVENT).getAsString()); } public static boolean isFinishEvent(String msg) { return isEvent(msg, WebSocketEventType.TASK_FINISHED); } public static boolean isStartEvent(String msg) { return isEvent(msg, WebSocketEventType.TASK_STARTED); } public static boolean isFailedEvent(String msg) { return isEvent(msg, WebSocketEventType.TASK_FAILED); } private static Status parseStatus(Status status) { if (status.isJson()) { JsonObject json = JsonUtils.parse(status.getMessage()); return Status.builder() .code(json.get(CODE) == null ? null : json.get(CODE).getAsString()) .statusCode(status.getStatusCode()) .message(json.get(MESSAGE) == null ? null : json.get(MESSAGE).getAsString()) .usage(json.getAsJsonObject(USAGE)) .requestId(json.get(REQUEST_ID) == null ? null : json.get(REQUEST_ID).getAsString()) .isJson(false) .build(); } return status; } private static ApiException checkApiKey(Map headers) { if (headers == null || headers.get("Authorization") == null) { return new ApiException( Status.builder() .statusCode(HttpStatus.SC_UNAUTHORIZED) .code(ErrorType.API_KEY_ERROR.getValue()) .message(ErrorType.API_KEY_ERROR.getValue()) .build()); } return null; } private static Exception parseThrowable(Throwable e) { if (e instanceof ApiException) { var apiException = (ApiException) e; apiException.setStatus(parseStatus(apiException.getStatus())); return apiException; } return new ApiException(e); } private static void doSend( Emitter emitter, Protocol protocol, String url, Map headers, Request request, HttpMethod httpMethod, StreamingMode mode) { var e = checkApiKey(headers); if (e != null) { emitter.onError(e); return; } switch (protocol) { case HTTP: HttpRpc httpRpc = new HttpRpc(url, new RpcResponseCallback<>(emitter, input -> input, null)); switch (httpMethod) { case GET: httpRpc.get(headers); break; case POST: if (mode == StreamingMode.NONE) { httpRpc.post(headers, request); } else { httpRpc.sse(headers, request); } break; default: emitter.onError( new ApiException( Status.builder() .statusCode(HttpStatus.SC_BAD_REQUEST) .code(ErrorType.PROTOCOL_UNSUPPORTED.getValue()) .message("Invalid Http Method: " + httpMethod) .build())); break; } break; case WEBSOCKET: try { WebsocketRpc websocketRpc = WebsocketPool.getInstance().getWsClient(url, headers); websocketRpc.call( request, new RpcResponseCallback<>(emitter, input -> input, websocketRpc), ServiceFacility::isFinishEvent); } catch (Exception ex) { emitter.onError(ex instanceof ApiException ? ex : new ApiException(ex)); } break; } } private static Response prepareResponsePayload(Protocol protocol, Response response) { if (protocol == Protocol.WEBSOCKET) { if (response.getMessage() != null) { JsonObject jsonObject = JsonUtils.parse(response.getMessage()); JsonObject header = jsonObject.getAsJsonObject(HEADER); Map map = Maps.newHashMap(); for (Map.Entry entry : header.entrySet()) { map.put(entry.getKey(), entry.getValue().getAsString()); } response.setHeaders(map); response.setMessage(JsonUtils.toJson(jsonObject.getAsJsonObject(PAYLOAD))); } } return response; } static class RpcResponseCallback extends ResultCallback { private final Emitter emitter; private final ResultCallback callback; private volatile WebsocketRpc websocketRpc; private final Function converter; public RpcResponseCallback( Emitter emitter, Function converter, WebsocketRpc websocketRpc) { this.emitter = emitter; this.callback = null; this.converter = converter; this.websocketRpc = websocketRpc; } public RpcResponseCallback( ResultCallback callback, Function converter, WebsocketRpc websocketRpc) { this.callback = callback; this.emitter = null; this.converter = converter; this.websocketRpc = websocketRpc; } public void onOpen(Status status) { if (callback != null) { callback.onOpen(parseStatus(status)); } } @Override public void onEvent(Response message) { if (isFailedEvent(message.getMessage())) { JsonObject jsonObject = JsonUtils.parse(message.getMessage()); WsHeader wsHeader = JsonUtils.fromJsonObject(jsonObject.getAsJsonObject(HEADER), WsHeader.class); JsonObject payload = jsonObject.getAsJsonObject(PAYLOAD); JsonObject usage = payload == null ? null : payload.getAsJsonObject(USAGE); Status status = Status.builder() .message(wsHeader.getErrorMessage()) .statusCode(44) .code(wsHeader.getErrorName()) .requestId(wsHeader.getTaskId()) .usage(usage) .build(); if (emitter != null) { emitter.onError(new ApiException(status)); } else { assert callback != null; callback.onError(new ApiException(status)); } } else { try { T result = converter.apply(message); if (emitter != null) { emitter.onNext(result); } else { assert callback != null; callback.onEvent(result); } } catch (Exception e) { if (emitter != null) { emitter.onError(e instanceof ApiException ? e : new ApiException(e)); } else { assert callback != null; callback.onError(e instanceof ApiException ? e : new ApiException(e)); } } } } @Override public void onComplete() { if (emitter != null) { emitter.onComplete(); } else { assert callback != null; callback.onComplete(); } if (this.websocketRpc != null) { synchronized (this) { if (this.websocketRpc != null) { WebsocketPool.getInstance().returnWsClient(this.websocketRpc); this.websocketRpc = null; } } } } @Override protected void finalize() throws Throwable { if (this.websocketRpc != null) { synchronized (this) { if (this.websocketRpc != null) { WebsocketPool.getInstance().returnWsClient(this.websocketRpc); this.websocketRpc = null; } } } super.finalize(); } @Override public void onError(Exception e) { if (emitter != null) { emitter.onError(parseThrowable(e)); } else { assert callback != null; callback.onError(parseThrowable(e)); } } public void onClose(Status status) { if (callback != null) { callback.onClose(parseStatus(status)); } } public void doClose(Status status) { if (callback != null) { callback.doClose(parseStatus(status)); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy