Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET WebApi SelfHost service fails on HTTP URL registration

I am trying to host an ASP.NET WebApi endpoint on an Azure worker role using the new Microsoft.AspNet.WebApi.SelfHost NuGet package. My worker's Run() code looks roughly like this:

// Endpoint is defined as in ServiceDefinition.csdef as 
//   HTTP, external port 8080, internal port 8080 (or 8081 - error both ways)
RoleInstanceEndpoint externalEndPoint =
    RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["Endpoint"]; 
string baseAddress= String.Format("http://{0}", externalEndPoint.IPEndpoint);
var maxsize = 1024 * 1024;  
var config = new HttpSelfHostConfiguration(baseAddress) 
{ 
    MaxBufferSize = maxsize, MaxReceivedMessageSize = maxsize 
};
config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

// Create and open the server
var server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();

// keep the worker thread alive
while (true)
    Thread.Sleep(Timeout);

This works fine in the dev fabric, but when deploying to Azure, I get an AggregateException from the server.OpenAsync() call, containing the following exception stack:

[0] One or more errors occurred.
[1] HTTP could not register URL http://+:8081/. Your process does not have access rights to this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details).
[2] Access is denied

I'm just running a vanilla worker role and this seems to be the "hello world" of self-host...

The endpoint part of my ServiceDefinition.csdef looks like this:

<Endpoints>
  <InputEndpoint name="Endpoint" protocol="http" port="8080" localPort="8081" />
</Endpoints>

The baseAddress that I get from the RoleEnvironment InstanceEndpoint looks legit - http://10.115.[X].[Y]:8081

I see the failure whether I use the same port/localPort (8080) or when I do a mapping, like the above.

It's clear that it's possible to host a conventional WCF service in a worker role in this way - is there any reason why ASP.NET WebApi SelfHost wouldn't work in this configuration?

like image 521
Omri Gazitt Avatar asked Jun 28 '12 07:06

Omri Gazitt


2 Answers

By default, the RoleEntryPoint runs under a very minimal permission user account for security. As the error indicates, it is unable to reserve that port due to those permissions. You have two options here:

  1. Run the Worker process as SYSTEM by adding the Runtime element to your role definition (i.e <Runtime executionContext="elevated"/>).
  2. Create a startup script that runs elevated and reserves that port for you.

For playing around (and troubleshooting if it is a permission issue), doing #1 is a quick way to test it.

Edit: I seem to recall a permission issue with WCF and Windows Azure when doing wildcard reservations. It used to work fine when using the full hostname e.g.

host.AddServiceEndpoint(
 typeof(IEchoService), new BasicHttpBinding(BasicHttpSecurityMode.None) { HostNameComparisonMode = HostNameComparisonMode.Exact }, "echo");
like image 115
dunnry Avatar answered Oct 21 '22 17:10

dunnry


After half a dev-day experimenting with invoking netsh.exe from an elevated startup script to no avail, I gave up and ended up using the big hammer and taking Ryan's initial suggestion of running the entire worker role elevated:

<WorkerRole name="WorkerRole" vmsize="ExtraSmall">
  <Runtime executionContext="elevated">
  </Runtime>
</WorkerRole>

That solved all issues.

For reference, here is how I tried to allow HTTP registration for non-elevated user accounts (which never really worked):

In ServiceDefinition.csdef:

<Startup>
  <Task executionContext="elevated" commandLine="startup\Install.cmd">
    <Environment>
      <Variable name="ENDPOINTPORT ">
        <RoleInstanceValue xpath="/RoleEnvironment/CurrentInstance/Endpoints/Endpoint[@name='Endpoint']/@port" />
      </Variable>
    </Environment>
  </Task>
</Startup>
<Endpoints>
  <InputEndpoint name="Endpoint" protocol="http" port="8080" localPort="8080" />
</Endpoints>

And in the startup script (startup\Install.cmd in my case):

netsh.exe http add urlacl url=http://+:%ENDPOINTPORT%/api user=everyone listen=yes delegate=yes

This is basically the solution that was recommended by the good folks working on AspNetWebApi (just a shorter way of doing what they recommend here), but unfortunately it didn't work for me - while the netsh command executed successfully and I was able to verify that the urlacl on the URL I am self-hosting on (http://+:8080/api/) is allowed by \Everyone, I was still getting the same permission error. If anyone figures out how to make this work when running the worker-role non-elevated, please post!

like image 27
Omri Gazitt Avatar answered Oct 21 '22 17:10

Omri Gazitt