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 'Thrift.Transport.THttpHandler' 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.