1

Does Cloud Foundry support Thrift for talking between apps? I can't find anything about this and it seems REST/HTTP or messaging are the preferred mechanisms for inter-app communication.

Has anyone tried to offer a service using Thrift in CF? Is there any good reason why Thrift is not supported; or are there any plans to support it? I prefer Thrift to REST for cross-language communication because it's easy in Thrift to generate all the client-side classes used in the API calls. Plus it supports binary data transfer and so is potentially faster than REST. Plus we already have a Thrift API:).

I'm guessing that - in theory - a client app could talk directly to another CF app instance running a Thrift service; but then you'd be losing the load-balancing advantages that CF currently provides for HTTP or messaging.

snark
  • 2,462
  • 3
  • 32
  • 63

3 Answers3

2

The Cloud Foundry Router currently supports routing of HTTP/HTTPS requests only. There is an effort underway to create a TCP Router to handle routing of non-HTTP traffic to apps running on CF.

There is a video that previews some of the capabilities the TCP Router will provide, and an incubating release that includes the TCP Router if you are deploying your own CF via BOSH.

The cf-dev mailing list would be a good place to ask additional questions about this.

I'm guessing that - in theory - a client app could talk directly to another CF app instance running a Thrift service

This is correct - apps running on CF can talk to each other using any protocol as long as the configured security groups have the proper ports open.

Scott Frederick
  • 4,184
  • 19
  • 22
  • 1
    I have no knowledge about Cloud Foundry, but maybe it helps. We built a solution using an existing HTTP(S) server. Only the deserialization of the request data is done via Thrift, the server itself is a standard product with our extension module plugged in. That way we don't need to run our own server, get all the product features and have to deal only with deserialization, feeding the Thrift processor with requests, and returning results the same way back. On top, the infrastructure for this is very simple, literally only a few lines of code. Works well with all Thrift HTTP clients. – JensG Feb 12 '16 at 00:05
  • 1
    What I am saying, is this: Using only the serialization part from Thrift can be helpful with non-standard communication channels. Depending on the capabilities of the channel, serialize into one of the binary formats, or, if binary is not an option, into JSON. Transfer your data by whatever means and deserialize the stuff at the other end. This allows to use Thrift even with loosely coupled systems, such as a message bus, MQs or the like. – JensG Feb 12 '16 at 00:11
  • Thanks; you've both given me plenty to think about! It looks like my options are (1) Stick with Thrift/TCP but without load-balancing until CF provide this TCP Router. Scott: Could you estimate when this is likely to become part of a regular CF release? (2) Use Thrift/HTTP as Jens says. @JensG: Were there important restrictions over what you could send over HTTP vs Thrift? Also, I'd like to know more about how a client would call different methods on your service. Did you have one generalised HTTP method or did you end up with a REST-like API that mirrored your Thrift API's methods? – snark Feb 12 '16 at 10:17
1

Were there important restrictions over what you could send over HTTP vs Thrift? Also, I'd like to know more about how a client would call different methods on your service. Did you have one generalised HTTP method or did you end up with a REST-like API that mirrored your Thrift API's methods?

Below the code that handles an incoming request and returns the results. The code is part of an article I wrote for a german dev magazine some time ago. The whole package consisted of some JavaScript, some C# and some Delphi code. I might emphasize that we also use that same approach shown here in real code.

Delphi comes with built-in support for ISAPI. The OnHandlerAction is basically an event handler that can be assigned to selected URL endpoints, or to catch all requests. This also implies, that we don't need to care about thread management, because that's what the IIS does for us.

Server side

I decided to use the TJSONProtocol for two reasons. First, one of the clients was the JavaScript part which at that time was only capable to speak JSON. Second goal was to eliminate any risk of garbled data. I never tested TBinaryProtocol nor TCompactProtocol in that context, so I can't say much about that combination.

procedure TSample.OnHandlerAction( Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
const GENERIC_ERROR_JSON = '[1,"TextToolIsapi",2,0,{"2":{"rec":{"1":{"str":"Internal error: %s"}}}}]';
var b : TBytes;
    s : string;
begin
  try
    Response.Content := TTextToolJsonServer.Process( Request.Content);
  except
    on e:Exception do begin
      b := ( SysUtils.TEncoding.UTF8.GetBytes(e.message));
      SetString( s, PAnsiChar(@b[0]), Length(b));
      Response.Content := Format( GENERIC_ERROR_JSON, [s]);
    end;
  end;
end;

The remaining part is the piece that does the (de)serializing stuff and feeds the processor. In this sample code a processor instance is created for every call. If that does not fit, one can think of a pool of processor instances or the like. In our case the session-specific data are held elsewhere, so the processors are in fact very lightweight.

class function TTextToolJsonServer.Process( const aRequest : string) : string;
var handler   : Samples.TTextTool.Iface;
    processor : IProcessor;
    protIn, protOut : IProtocol;
    stmIn, stmOut : TStringStream;
begin
  stmIn  := nil;
  stmOut := nil;
  try
    stmIn  := TStringStream.Create( aRequest);
    stmOut := TStringStream.Create;

    protIn  := TJSONProtocolImpl.Create(
                 TStreamTransportImpl.Create(
                   TThriftStreamAdapterDelphi.Create( stmIn, FALSE), nil));

    protOut := TJSONProtocolImpl.Create(
                 TStreamTransportImpl.Create(
                   nil, TThriftStreamAdapterDelphi.Create( stmOut, FALSE)));

    handler   := TTextToolHandler.Create;
    processor := Samples.TTextTool.TProcessorImpl.Create( handler);
    processor.Process( protIn, protOut);

    result := stmOut.DataString;

  finally
    stmIn.Free;
    stmOut.Free;
  end;
end;

That's all, the rest is standard processor/handler implementation.

Sample client

The client side counterpart could be as simple as this piece of JavaScript. Unfortunately I don't have a Java example at hand, but it would look very similar.

  function MakeClient()
  {
    var transport = new Thrift.Transport("bin/TextToolSample.dll"); 
    var protocol  = new Thrift.Protocol(transport);
    var client = new Samples.TextToolClient(protocol);
    return client;
  }

  function CountWords() 
  {
    try
    {
      var client = MakeClient();
      var nCount = client.CountWords( document.EDITOR.textinput.value);
      alert("Text contains "+nCount.toString()+" words.");
    }
    catch (error) 
    {
      HandleError( error) 
    }
  }
JensG
  • 13,148
  • 4
  • 45
  • 55
  • Thanks for sharing the details but I don't understand what a client, in Java say, would do if they wanted to call your service. If HTTP is the transport then is the client sending a REST request which contains parameters serialized by Thrift? For each service method in your Thrift IDL do you then need a corresponding REST method? Or are you somehow able to invoke *any* of your Thrift service methods via a *single* REST API call and then unpack it at the server end before invoking the appropriate service method? i.e., Does the REST client see one method in total, or one for every Thrift method? – snark Feb 13 '16 at 10:23
  • It's a simple HTTP request, as every Thrift HTTP-capable client sends and receives them. There is no REST involved, and I don't know why you want to bring it into the game. REST is not the holy grail that magically solves all problems (in fact, sometime REST *is* the problem). But of course, theoretically REST could be the vehicle to transport a Thrift message. As I wrote above, the modularity of Thrift has been proven to work with 0MQ and messaging systems, so it is probably a safe bet to say that anything goes. – JensG Feb 13 '16 at 11:59
  • I added the JavaScript client part. – JensG Feb 13 '16 at 12:08
  • Sorry, I'm missing something here! Your client looks like a regular Thrift client. I thought the whole gist of your original answer was simply to use Thrift for (de)serialisation so that HTTP could be used for transferring the messages. That way, I could then benefit from Cloud Foundry's inbuilt routing mechanism for HTTP. I have no particular desire at all to bring in REST; I prefer Thrift to REST myself. If you can explain how to use Thrift over HTTP without using REST that would be great:). – snark Feb 13 '16 at 17:29
  • 1
    "*If you can explain how to use Thrift over HTTP without using REST that would be great*" --- I did that already. The message body of the HTTP request *is* the serialized message, same with the response body. It really is that simple. HTTP transports a message, whose message body happens to be in Thrift JSON format. Both server and client know this, and all the other infrastructure in between doesn't care about it. They just see HTTP requests and responses, and that's all they need to know. – JensG Feb 13 '16 at 18:22
  • Ok, thanks. I didn't appreciate that Thrift already had HTTP clients; even though you mentioned it, and I now see they're listed at https://wiki.apache.org/thrift/LibraryFeatures?action=show&redirect=LanguageSupport. So that _does_ dramatically simplify the client-side of things:). Our existing service implementation is in C#. The previous link and https://issues.apache.org/jira/browse/THRIFT-322 imply there's a C# HTTP server. So we could use that ... if it's ready? Or else add an OnHandlerAction() in C#, like you did in Delphi, so we could use IIS instead for thread management. – snark Feb 14 '16 at 16:26
  • Is there any good reason _not_ to use the C# HTTP server mentioned in my previous comment; or would you still recommend implementing the plug-in for IIS as you suggested before? From the performance graph in [Randy Abernethy's article](https://dzone.com/articles/polyglot-microservices-and-apache-thrift) it also looks like I can expect a roughly 5-6 fold reduction in performance by switching from TCP to HTTP, unfortunately. – snark Mar 04 '16 at 12:19
  • Whatever fits best for your purpose is right. I can't tell you that, only show some options. – JensG Mar 04 '16 at 13:03
0

Following on from @JensG's suggested solution I wanted to find a solution that used Thrift/HTTP. This is a fallback in case your CF admins don't want to support Thrift/TCP if, for example, the absence of a .Net buildpack in your CF deployment means the Thrift service has to be hosted outside CF. I'm sharing it here because it wasn't obvious to me how to get Thrift/HTTP working for a C# service implementation. Thrift 0.9.3 was used throughout.

Sample Thrift IDL file

namespace csharp AuthServiceGenCSharp
namespace * AuthServiceGenCSharp.thrift

struct Request
{
    1:string name
    2:i32 age
}

struct Result
{
    1:string reply
    2:bool permissionGranted
}

service AuthorizationService
{
    Result AskPermission(1:Request req)
}

Use the Thrift compiler to generate the C# stubs as normal. Then implement the service:

Sample Service Implementation (C#)

using AuthServiceGenCSharp;

namespace AuthServiceImplementation
{
    /// <summary>
    /// Implementation of the Thrift interface.
    /// </summary>
    public class AuthorizationServiceImpl : AuthorizationService.Iface
    {
        public Result AskPermission(Request req)
        {
            Result result = new Result();
            result.Reply = "Hello " + req.Name;
            result.PermissionGranted = true;
            return result;
        }
    }
}

Create a custom HTTP handler

Fortunately Thrift already provides a class that implements the IHttpHandler interface: Thrift.Transport.THttpHandler. All you have to do is derive your handler from THttpHandler and pass in the processor that the Thrift compiler generated from your IDL. And specify the JSON protocol:

using AuthServiceGenCSharp;
using AuthServiceImplementation;
using Thrift.Protocol;
using Thrift.Transport;

// See also https://codealoc.wordpress.com/2012/04/06/thrift-over-http-with-iis/
namespace AuthServiceHTTPHandler.App_Code
{
    public class AuthServiceHandler : THttpHandler
    {
        public AuthServiceHandler()
            : base(CreateAuthProcessor(), CreateJSONFactory())
        {
            // We call the constructor for THttpHandler, passing in the processor we want to use (i.e., the one that
            // the Thrift compiler generated from our IDL) and the protocol factory for JSON in this case.
            // We can't use THttpHandler directly for 2 reasons. First, it doesn't have a no-arg constructor which
            // all proper handlers must have. (At runtime you'll get this error:
            // HTTP/1.1 500 Internal Server Error [MissingMethodException: Constructor on type &#39;Thrift.Transport.THttpHandler&#39; not found.])
            // Second, we need to tell THttpHandler which processor to use at the very least.
            // Maybe Thrift should make their THttpHandler class abstract to prevent people like me from trying to use it directly!?
        }

        private static AuthorizationService.Processor CreateAuthProcessor()
        {
            return new AuthorizationService.Processor(new AuthorizationServiceImpl());
        }

        private static TJSONProtocol.Factory CreateJSONFactory()
        {
            return new TJSONProtocol.Factory();
        }
    }
}

(A similar example is given on the codealoc blog.) Put this handler in the App_Code folder of your ASP.Net web application. Register the handler in your web.config. I'm using IIS 7.5 in integrated mode so I did this:

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <handlers>
      <remove name="WebDAV" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <add name="AuthServiceHandler"
           path="*."
           verb="*"
           type="AuthServiceHTTPHandler.App_Code.AuthServiceHandler"
           preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>

Sample Java Client snippet

import org.apache.thrift.TException;
import org.apache.thrift.protocol.TJSONProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import AuthServiceGenCSharp.thrift.AuthorizationService.Client;
import AuthServiceGenCSharp.thrift.Request;
import AuthServiceGenCSharp.thrift.Result;

...

TTransport transport = null;
try {
    transport = new THttpClient("http://localhost:9999");
} catch (TTransportException e) {
    e.printStackTrace();
}
TProtocol protocol = new TJSONProtocol(transport);
Client client = new Client(protocol);

try {
    transport.open();
    Request request = new Request("Fred", 32);
    Result result = client.AskPermission(request);
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(result);
    log.info("Response in JSON format: " + json);
    return json;
} catch (TException e) {
    e.printStackTrace();                
} catch (JsonProcessingException e) {                               
    e.printStackTrace();
} finally {
    if (transport != null && transport.isOpen()) {
        transport.close();
    }
}

When the C# ASP.NET app was published from Visual Studio 2010 to IIS 7.5 and the Java client pointed at it, the Thrift response in the body of the HTTP response was:

[1,"AskPermission",2,1,{"0":{"rec":{"1":{"str":"Hello Fred"},"2":{"tf":1}}}}]

and the Java log output:

Response in JSON format: {"reply":"Hello Fred","permissionGranted":true,"setPermissionGranted":true,"setReply":true}

I originally tried implementing a custom HTTP module instead. But, although the correct Thrift response was coming back in the HTTP response, the HTTP header was always 405: Method Not Allowed (the Thrift client uses POST to send its requests). There's another SO post about this. However using an HTTP handler might be better than using a module in the long run because you have the option of creating an asynchronous handler for long-running requests.

Community
  • 1
  • 1
snark
  • 2,462
  • 3
  • 32
  • 63
  • Later on I ran into the dreaded HTTP 405 error again with the HTTP handler when it was deployed to IIS. I worked around it by creating a dummy web page and binding the handler to that page for POST requests. See http://stackoverflow.com/a/36749011/1843329 for details. – snark Apr 21 '16 at 10:17