[ 
https://issues.apache.org/jira/browse/NIFI-1857?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15318553#comment-15318553
 ] 

ASF GitHub Bot commented on NIFI-1857:
--------------------------------------

Github user ijokarumawak commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/497#discussion_r66080066
  
    --- Diff: 
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java
 ---
    @@ -86,12 +156,958 @@ public Response getController() {
             final ControllerEntity entity = new ControllerEntity();
             entity.setController(controller);
     
    +        if (isEmpty(req.getHeader(HttpHeaders.PROTOCOL_VERSION))) {
    +            // This indicates the client uses older NiFi version,
    +            // which strictly read JSON properties and fail with unknown 
properties.
    +            // Convert result entity so that old version clients can 
understance.
    +            logger.debug("Converting result to provide backward 
compatibility...");
    +            controller.setRemoteSiteHttpListeningPort(null);
    +        }
    +
             // generate the response
             return clusterContext(noCache(Response.ok(entity))).build();
         }
     
    +    private Response.ResponseBuilder 
setCommonHeaders(Response.ResponseBuilder builder, Integer 
transportProtocolVersion) {
    +        return builder.header(HttpHeaders.PROTOCOL_VERSION, 
transportProtocolVersion)
    +                .header(HttpHeaders.SERVER_SIDE_TRANSACTION_TTL, 
transactionManager.getTransactionTtlSec());
    +    }
    +
    +    private Integer negotiateTransportProtocolVersion(@Context 
HttpServletRequest req) throws BadRequestException {
    +        String protocolVersionStr = 
req.getHeader(HttpHeaders.PROTOCOL_VERSION);
    +        if (isEmpty(protocolVersionStr)) {
    +            throw new BadRequestException("Protocol version was not 
specified.");
    +        }
    +
    +        final Integer requestedProtocolVersion;
    +        try {
    +            requestedProtocolVersion = Integer.valueOf(protocolVersionStr);
    +        } catch (NumberFormatException e) {
    +            throw new BadRequestException("Specified protocol version was 
not in a valid number format: " + protocolVersionStr);
    +        }
    +
    +        Integer protocolVersion;
    +        if 
(transportProtocolVersionNegotiator.isVersionSupported(requestedProtocolVersion))
 {
    +            return requestedProtocolVersion;
    +        } else {
    +            protocolVersion = 
transportProtocolVersionNegotiator.getPreferredVersion(requestedProtocolVersion);
    +        }
    +
    +        if (protocolVersion == null) {
    +            throw new BadRequestException("Specified protocol version is 
not supported: " + protocolVersionStr);
    +        }
    +        return protocolVersion;
    +    }
    +
    +
    +    /**
    +     * Returns the details of this NiFi.
    +     *
    +     * @param clientId Optional client id. If the client id is not 
specified, a new one will be generated. This value (whether specified or 
generated) is included in the response.
    +     * @return A controllerEntity.
    +     */
    +    @GET
    +    @Path("/peers")
    +    @Consumes(MediaType.WILDCARD)
    +    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    +    // TODO: @PreAuthorize("hasRole('ROLE_NIFI')")
    +    @ApiOperation(
    +            value = "Returns the details about this NiFi necessary to 
communicate via site to site",
    +            response = PeersEntity.class,
    +            authorizations = @Authorization(value = "NiFi", type = 
"ROLE_NIFI")
    +    )
    +    @ApiResponses(
    +            value = {
    +                    @ApiResponse(code = 400, message = "NiFi was unable to 
complete the request because it was invalid. The request should not be retried 
without modification."),
    +                    @ApiResponse(code = 401, message = "Client could not 
be authenticated."),
    +                    @ApiResponse(code = 403, message = "Client is not 
authorized to make this request."),
    +                    @ApiResponse(code = 409, message = "The request was 
valid but NiFi was not in the appropriate state to process it. Retrying the 
same request later may be successful.")
    +            }
    +    )
    +    public Response getPeers(
    +            @ApiParam(
    +            value = "If the client id is not specified, new one will be 
generated. This value (whether specified or generated) is included in the 
response.",
    +            required = false
    +    )
    +    @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) 
ClientIdParameter clientId,
    +    @Context HttpServletRequest req) {
    +
    +        if (!properties.isSiteToSiteHttpEnabled()) {
    +            return responseCreator.httpSiteToSiteIsNotEnabledResponse();
    +        }
    +
    +        final Integer transportProtocolVersion;
    +        try {
    +            transportProtocolVersion = 
negotiateTransportProtocolVersion(req);
    +        } catch (BadRequestException e) {
    +            return responseCreator.badRequestResponse(e);
    +        }
    +
    +        ArrayList<PeerDTO> peers;
    +
    +        if (properties.isNode()) {
    +            return responseCreator.nodeTypeErrorResponse(req.getPathInfo() 
+ " is only accessible on NCM or Standalone NiFi instance.");
    +        // TODO: NCM no longer exists.
    +        /*
    +        } else if (properties.isClusterManager()) {
    +            ClusterNodeInformation clusterNodeInfo = 
clusterManager.getNodeInformation();
    +            final Collection<NodeInformation> nodeInfos = 
clusterNodeInfo.getNodeInformation();
    +            peers = new ArrayList<>(nodeInfos.size());
    +            for (NodeInformation nodeInfo : nodeInfos) {
    +                if (nodeInfo.getSiteToSiteHttpApiPort() == null) {
    +                    continue;
    +                }
    +                PeerDTO peer = new PeerDTO();
    +                peer.setHostname(nodeInfo.getSiteToSiteHostname());
    +                peer.setPort(nodeInfo.getSiteToSiteHttpApiPort());
    +                peer.setSecure(nodeInfo.isSiteToSiteSecure());
    +                peer.setFlowFileCount(nodeInfo.getTotalFlowFiles());
    +                peers.add(peer);
    +            }
    +        */
    +        } else {
    +            // Standalone mode.
    +            PeerDTO peer = new PeerDTO();
    +            // req.getLocalName returns private IP address, that can't be 
accessed from client in some environments.
    +            // So, use the value defined in nifi.properties instead when 
it is defined.
    +            String remoteInputHost = properties.getRemoteInputHost();
    +            peer.setHostname(isEmpty(remoteInputHost) ? req.getLocalName() 
: remoteInputHost);
    +            peer.setPort(properties.getRemoteInputHttpPort());
    +            peer.setSecure(properties.isSiteToSiteSecure());
    +            peer.setFlowFileCount(0);  // doesn't matter how many 
FlowFiles we have, because we're the only host.
    +
    +            peers = new ArrayList<>(1);
    +            peers.add(peer);
    +
    +        }
    +
    +        PeersEntity entity = new PeersEntity();
    +        entity.setPeers(peers);
    +
    +        return 
clusterContext(noCache(setCommonHeaders(Response.ok(entity), 
transportProtocolVersion))).build();
    +    }
    +
    +    @POST
    +    @Produces(MediaType.APPLICATION_JSON)
    +    @Path("{portType}/{portId}/transactions")
    +    // TODO - @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 
'ROLE_ADMIN')")
    +    @ApiOperation(
    +            value = "Create a transaction to this output port",
    +            response = TransactionResultEntity.class,
    +            authorizations = {
    +                    @Authorization(value = "Read Only", type = 
"ROLE_MONITOR"),
    +                    @Authorization(value = "Data Flow Manager", type = 
"ROLE_DFM"),
    +                    @Authorization(value = "Administrator", type = 
"ROLE_ADMIN")
    +            }
    +    )
    +    @ApiResponses(
    +            value = {
    +                    @ApiResponse(code = 400, message = "NiFi was unable to 
complete the request because it was invalid. The request should not be retried 
without modification."),
    +                    @ApiResponse(code = 401, message = "Client could not 
be authenticated."),
    +                    @ApiResponse(code = 403, message = "Client is not 
authorized to make this request."),
    +                    @ApiResponse(code = 404, message = "The specified 
resource could not be found."),
    +                    @ApiResponse(code = 409, message = "The request was 
valid but NiFi was not in the appropriate state to process it. Retrying the 
same request later may be successful.")
    +            }
    +    )
    +    public Response createPortTransaction(
    +            @ApiParam(
    +                    value = "If the client id is not specified, new one 
will be generated. This value (whether specified or generated) is included in 
the response.",
    +                    required = false
    +            )
    +            @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) 
ClientIdParameter clientId,
    +            @ApiParam(
    +                    value = "The input port id.",
    +                    required = true
    +            )
    +            @PathParam("portType") String portType,
    +            @PathParam("portId") String portId,
    +            @Context HttpServletRequest req,
    +            @Context ServletContext context,
    +            @Context UriInfo uriInfo,
    +            InputStream inputStream) {
    +
    +        final ValidateRequestResult validationResult = validateResult(req, 
clientId, portId);
    +        if (validationResult.errResponse != null) {
    +            return validationResult.errResponse;
    +        }
    +
    +        if(!PORT_TYPE_INPUT.equals(portType) && 
!PORT_TYPE_OUTPUT.equals(portType)){
    +            return responseCreator.wrongPortTypeResponse(clientId, 
portType, portId);
    +        }
    +
    +        logger.debug("createPortTransaction request: clientId={}, 
portType={}, portId={}", clientId.getClientId(), portType, portId);
    +
    +        final ByteArrayOutputStream out = new ByteArrayOutputStream();
    +        final String transactionId = 
transactionManager.createTransaction();
    +        final Peer peer = constructPeer(req, inputStream, out, portId, 
transactionId);
    +        final int transportProtocolVersion = 
validationResult.transportProtocolVersion;
    +
    +        try {
    +            // Execute handshake.
    +            initiateServerProtocol(peer, transportProtocolVersion);
    +
    +            TransactionResultEntity entity = new TransactionResultEntity();
    +            entity.setResponseCode(ResponseCode.PROPERTIES_OK.getCode());
    +            entity.setMessage("Handshake properties are valid, and port is 
running. A transaction is created:" + transactionId);
    +
    +            return responseCreator.locationResponse(uriInfo, portType, 
portId, transactionId, entity, transportProtocolVersion);
    +
    +        } catch (HandshakeException e) {
    +            transactionManager.cancelTransaction(transactionId);
    +            return responseCreator.handshakeExceptionResponse(e);
    +
    +        } catch (Exception e) {
    +            transactionManager.cancelTransaction(transactionId);
    +            return responseCreator.unexpectedErrorResponse(clientId, 
portId, e);
    +        }
    +    }
    +
    +    @POST
    +    @Consumes(MediaType.APPLICATION_OCTET_STREAM)
    +    @Produces(MediaType.TEXT_PLAIN)
    +    @Path("input-ports/{portId}/transactions/{transactionId}/flow-files")
    +    // TODO - @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 
'ROLE_ADMIN')")
    +    @ApiOperation(
    +            value = "Transfer flow files to input port",
    +            response = TransactionResultEntity.class,
    +            authorizations = {
    +                    @Authorization(value = "Read Only", type = 
"ROLE_MONITOR"),
    +                    @Authorization(value = "Data Flow Manager", type = 
"ROLE_DFM"),
    +                    @Authorization(value = "Administrator", type = 
"ROLE_ADMIN")
    +            }
    +    )
    +    @ApiResponses(
    +            value = {
    +                    @ApiResponse(code = 400, message = "NiFi was unable to 
complete the request because it was invalid. The request should not be retried 
without modification."),
    +                    @ApiResponse(code = 401, message = "Client could not 
be authenticated."),
    +                    @ApiResponse(code = 403, message = "Client is not 
authorized to make this request."),
    +                    @ApiResponse(code = 404, message = "The specified 
resource could not be found."),
    +                    @ApiResponse(code = 409, message = "The request was 
valid but NiFi was not in the appropriate state to process it. Retrying the 
same request later may be successful.")
    +            }
    +    )
    +    public Response receiveFlowFiles(
    +            @ApiParam(
    +                    value = "If the client id is not specified, new one 
will be generated. This value (whether specified or generated) is included in 
the response.",
    +                    required = false
    +            )
    +            @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) 
ClientIdParameter clientId,
    +            @ApiParam(
    +                    value = "The input port id.",
    +                    required = true
    +            )
    +            @PathParam("portId") String portId,
    +            @PathParam("transactionId") String transactionId,
    +            @Context HttpServletRequest req,
    +            @Context ServletContext context,
    +            @Context UriInfo uriInfo,
    +            InputStream inputStream) {
    +
    +        final ValidateRequestResult validationResult = validateResult(req, 
clientId, portId, transactionId);
    +        if (validationResult.errResponse != null) {
    +            return validationResult.errResponse;
    +        }
    +
    +        logger.debug("receiveFlowFiles request: clientId={}, portId={}", 
clientId.getClientId(), portId);
    +
    +        final ByteArrayOutputStream out = new ByteArrayOutputStream();
    +        final Peer peer = constructPeer(req, inputStream, out, portId, 
transactionId);
    +        final int transportProtocolVersion = 
validationResult.transportProtocolVersion;
    +
    +        try {
    +            HttpFlowFileServerProtocol serverProtocol = 
initiateServerProtocol(peer, transportProtocolVersion);
    +            int numOfFlowFiles = 
serverProtocol.getPort().receiveFlowFiles(peer, serverProtocol);
    +            logger.debug("finished receiving flow files, 
numOfFlowFiles={}", numOfFlowFiles);
    +            if (numOfFlowFiles < 1) {
    +                return Response.status(Response.Status.BAD_REQUEST)
    +                        .entity("Client should send request when there is 
data to send. There was no flow file sent.").build();
    +            }
    +        } catch (HandshakeException e) {
    +            return responseCreator.handshakeExceptionResponse(e);
    +
    +        } catch (NotAuthorizedException e) {
    +            return responseCreator.unauthorizedResponse(e);
    +
    +        } catch (BadRequestException | RequestExpiredException e) {
    +            return responseCreator.badRequestResponse(e);
    +
    +        } catch (Exception e) {
    +            return responseCreator.unexpectedErrorResponse(clientId, 
portId, e);
    +        }
    +
    +        String serverChecksum = 
((HttpServerCommunicationsSession)peer.getCommunicationsSession()).getChecksum();
    +        return responseCreator.acceptedResponse(serverChecksum, 
transportProtocolVersion);
    +    }
    +
    +    private HttpFlowFileServerProtocol initiateServerProtocol(Peer peer, 
Integer transportProtocolVersion) throws IOException {
    +        // Switch transaction protocol version based on transport protocol 
version.
    +        TransportProtocolVersionNegotiator 
negotiatedTransportProtocolVersion = new 
TransportProtocolVersionNegotiator(transportProtocolVersion);
    +        VersionNegotiator versionNegotiator = new 
StandardVersionNegotiator(negotiatedTransportProtocolVersion.getTransactionProtocolVersion());
    +        HttpFlowFileServerProtocol serverProtocol = new 
HttpFlowFileServerProtocol(versionNegotiator);
    +        
HttpRemoteSiteListener.getInstance().setupServerProtocol(serverProtocol);
    +        // TODO: How should I pass cluster information?
    +        // serverProtocol.setNodeInformant(clusterManager);
    +        serverProtocol.handshake(peer);
    +        return serverProtocol;
    +    }
    +
    +    private Peer constructPeer(HttpServletRequest req, InputStream 
inputStream, OutputStream outputStream, String portId, String transactionId) {
    +        String clientHostName = req.getRemoteHost();
    +        int clientPort = req.getRemotePort();
    +
    +        PeerDescription peerDescription = new 
PeerDescription(clientHostName, clientPort, req.isSecure());
    +
    +        HttpServerCommunicationsSession commSession = new 
HttpServerCommunicationsSession(inputStream, outputStream, transactionId);
    +
    +        boolean useCompression = false;
    +        final String useCompressionStr = 
req.getHeader(HANDSHAKE_PROPERTY_USE_COMPRESSION);
    +        if (!isEmpty(useCompressionStr) && 
Boolean.valueOf(useCompressionStr)) {
    +            useCompression = true;
    +        }
    +
    +        final String requestExpiration = 
req.getHeader(HANDSHAKE_PROPERTY_REQUEST_EXPIRATION);
    +        final String batchCount = 
req.getHeader(HANDSHAKE_PROPERTY_BATCH_COUNT);
    +        final String batchSize = 
req.getHeader(HANDSHAKE_PROPERTY_BATCH_SIZE);
    +        final String batchDuration = 
req.getHeader(HANDSHAKE_PROPERTY_BATCH_DURATION);
    +
    +        commSession.putHandshakeParam(HandshakeProperty.PORT_IDENTIFIER, 
portId);
    +        commSession.putHandshakeParam(HandshakeProperty.GZIP, 
String.valueOf(useCompression));
    +
    +        if (!isEmpty(requestExpiration)) 
commSession.putHandshakeParam(REQUEST_EXPIRATION_MILLIS, requestExpiration);
    +        if (!isEmpty(batchCount)) 
commSession.putHandshakeParam(BATCH_COUNT, batchCount);
    +        if (!isEmpty(batchSize)) commSession.putHandshakeParam(BATCH_SIZE, 
batchSize);
    +        if (!isEmpty(batchDuration)) 
commSession.putHandshakeParam(BATCH_DURATION, batchDuration);
    +
    +        if(peerDescription.isSecure()){
    +            NiFiUser nifiUser = NiFiUserUtils.getNiFiUser();
    +            logger.debug("initiating peer, nifiUser={}", nifiUser);
    +            commSession.setUserDn(nifiUser.getIdentity());
    +        }
    +
    +        // TODO: Followed how SocketRemoteSiteListener define peerUrl and 
clusterUrl, but it can be more meaningful values, especially for clusterUrl.
    +        String peerUrl = "nifi://" + clientHostName + ":" + clientPort;
    +        String clusterUrl = "nifi://localhost:" + req.getLocalPort();
    --- End diff --
    
    What can be a meaningful clusterUrl here?


> Support HTTP(S) as a transport mechanism for Site-to-Site
> ---------------------------------------------------------
>
>                 Key: NIFI-1857
>                 URL: https://issues.apache.org/jira/browse/NIFI-1857
>             Project: Apache NiFi
>          Issue Type: Improvement
>          Components: Core Framework
>            Reporter: Koji Kawamura
>            Assignee: Koji Kawamura
>   Original Estimate: 480h
>  Remaining Estimate: 480h
>
> We should add support for using HTTP(S) for site-to-site to be an alternative 
> to the current socket based approach.
> This would support the same push based or pull based approach site-to-site 
> offers now but it would use HTTP(S) for all interactions to include learning 
> about ports, learning about NCM topology, and actually exchanging data. This 
> mechanism should also support interaction via an HTTP proxy.
> This would also require some UI work to allow the user to specify which 
> protocol for site-to-site to use such as 'raw' vs 'http'. We also need to 
> document any limitations with regard to SSL support for this mode and we'd 
> need to provide 'how-to' when using proxies like http_proxy or something else.



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)

Reply via email to