code: /*
   * Copyright (c) 2019 the Eclipse Milo Authors
   *
   * This program and the accompanying materials are made
   * available under the terms of the Eclipse Public License 2.0
   * which is available at https://www.eclipse.org/legal/epl-2.0/
   *
   * SPDX-License-Identifier: EPL-2.0
   */
  
  package org.eclipse.milo.opcua.stack.client.transport.http;
  
  import java.util.List;
  
  import io.netty.buffer.ByteBuf;
  import io.netty.buffer.Unpooled;
  import io.netty.channel.ChannelHandlerContext;
  import io.netty.handler.codec.MessageToMessageCodec;
  import io.netty.handler.codec.http.DefaultFullHttpRequest;
  import io.netty.handler.codec.http.FullHttpRequest;
  import io.netty.handler.codec.http.FullHttpResponse;
  import io.netty.handler.codec.http.HttpHeaderNames;
  import io.netty.handler.codec.http.HttpHeaderValues;
  import io.netty.handler.codec.http.HttpMethod;
  import io.netty.handler.codec.http.HttpResponse;
  import io.netty.handler.codec.http.HttpResponseStatus;
  import io.netty.handler.codec.http.HttpVersion;
  import io.netty.util.AttributeKey;
  import org.eclipse.milo.opcua.stack.client.UaStackClient;
  import org.eclipse.milo.opcua.stack.client.UaStackClientConfig;
  import org.eclipse.milo.opcua.stack.client.transport.UaTransportRequest;
  import org.eclipse.milo.opcua.stack.core.StatusCodes;
  import org.eclipse.milo.opcua.stack.core.UaException;
  import org.eclipse.milo.opcua.stack.core.serialization.OpcUaBinaryStreamDecoder;
  import org.eclipse.milo.opcua.stack.core.serialization.OpcUaBinaryStreamEncoder;
  import org.eclipse.milo.opcua.stack.core.serialization.UaResponseMessage;
  import org.eclipse.milo.opcua.stack.core.transport.TransportProfile;
  import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
  import org.eclipse.milo.opcua.stack.core.util.EndpointUtil;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  public class OpcClientHttpCodec extends MessageToMessageCodec<HttpResponse, UaTransportRequest> {
  
      private static final AttributeKey<UaTransportRequest> KEY_PENDING_REQUEST =
          AttributeKey.newInstance("pendingRequest");
  
      private static final String UABINARY_CONTENT_TYPE =
          HttpHeaderValues.APPLICATION_OCTET_STREAM.toString();
  
      private final Logger logger = LoggerFactory.getLogger(getClass());
  
      private final UaStackClientConfig config;
      private final EndpointDescription endpoint;
      private final TransportProfile transportProfile;
  
      private final UaStackClient client;
  
      OpcClientHttpCodec(UaStackClient client) {
          this.client = client;
  
          config = client.getConfig();
          endpoint = config.getEndpoint();
          transportProfile = TransportProfile.fromUri(endpoint.getTransportProfileUri());
      }
  
      @Override
      protected void encode(
          ChannelHandlerContext ctx,
          UaTransportRequest transportRequest,
          List<Object> out) throws Exception {
  
          logger.debug("encoding: " + transportRequest.getRequest());
  
          ctx.channel().attr(KEY_PENDING_REQUEST).set(transportRequest);
  
          ByteBuf content = Unpooled.buffer();
  
          switch (transportProfile) {
              case HTTPS_UABINARY: {
                  OpcUaBinaryStreamEncoder encoder =
                      new OpcUaBinaryStreamEncoder(client.getStaticSerializationContext());
                  encoder.setBuffer(content);
                  encoder.writeMessage(null, transportRequest.getRequest());
                  break;
              }
  
              case HTTPS_UAXML: {
                  // TODO put document into a SOAP message.
                  throw new UaException(StatusCodes.Bad_InternalError,
                      "no encoder for transport: " + transportProfile);
              }
  
              default:
                  throw new UaException(StatusCodes.Bad_InternalError,
                      "no encoder for transport: " + transportProfile);
          }
  
          String endpointUrl = endpoint.getEndpointUrl();
  
          FullHttpRequest httpRequest = new DefaultFullHttpRequest(
              HttpVersion.HTTP_1_1,
              HttpMethod.POST,
              EndpointUtil.getPath(endpointUrl),
              content
          );
  
          httpRequest.headers().set(HttpHeaderNames.HOST, EndpointUtil.getHost(endpointUrl));
          httpRequest.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
          httpRequest.headers().set(HttpHeaderNames.CONTENT_TYPE, UABINARY_CONTENT_TYPE);
          httpRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
          httpRequest.headers().set("OPCUA-SecurityPolicy", config.getEndpoint().getSecurityPolicyUri());
  
          out.add(httpRequest);
      }
  
      @Override
      protected void decode(
          ChannelHandlerContext ctx,
          HttpResponse httpResponse,
          List<Object> out) throws Exception {
  
          logger.trace("channelRead0: " + httpResponse);
  
          UaTransportRequest transportRequest = ctx.channel()
              .attr(KEY_PENDING_REQUEST)
              .getAndSet(null);
  
          if (httpResponse instanceof FullHttpResponse) {
              String contentType = httpResponse.headers().get(HttpHeaderNames.CONTENT_TYPE);
  
              FullHttpResponse fullHttpResponse = (FullHttpResponse) httpResponse;
              ByteBuf content = fullHttpResponse.content();
  
              UaResponseMessage responseMessage;
  
              switch (transportProfile) {
                  case HTTPS_UABINARY: {
                      if (!UABINARY_CONTENT_TYPE.equalsIgnoreCase(contentType)) {
                          throw new UaException(StatusCodes.Bad_DecodingError,
                              "unexpected content-type: " + contentType);
                      }
  
                      OpcUaBinaryStreamDecoder decoder =
                          new OpcUaBinaryStreamDecoder(client.getStaticSerializationContext());
                      decoder.setBuffer(content);
                      responseMessage = (UaResponseMessage) decoder.readMessage(null);
                      break;
                  }
  
                  case HTTPS_UAXML: {
                      // TODO extract document from SOAP message body
                      throw new UaException(StatusCodes.Bad_InternalError,
                          "no decoder for transport: " + transportProfile);
                  }
  
                  default:
                      throw new UaException(StatusCodes.Bad_InternalError,
                          "no decoder for transport: " + transportProfile);
              }
  
              transportRequest.getFuture().complete(responseMessage);
          } else {
              HttpResponseStatus status = httpResponse.status();
  
              if (status.equals(HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE)) {
                  transportRequest.getFuture().completeExceptionally(
                      new UaException(StatusCodes.Bad_ResponseTooLarge));
              } else {
                  transportRequest.getFuture().completeExceptionally(
                      new UaException(StatusCodes.Bad_UnexpectedError,
                          String.format("%s: %s", status.code(), status.reasonPhrase())));
              }
          }
      }
  
  }
  