Azure / azure-functions-host

The host/runtime that powers Azure Functions
https://functions.azure.com
MIT License
1.94k stars 441 forks source link

Customize default route on domain root level #848

Closed mikezks closed 7 years ago

mikezks commented 8 years ago

[Feature] request

Current status

Possible implementation

App setting AzureWebJobsDefaultRoute:

  1. defaultPage Standard website ("Your Azure Function App is up and running.") -> default value
  2. emptyPage Empty website
  3. customPage Custom website
  4. function Custom Azure Function

AzureWebJobsDisableHomepage would be obsolete in this scenario.

Detailed settings in host.json:

{
    // Configuration settings for default route:
    "defaultRoute": {
        // Defines the path for a custom website which will respond to requests to the default route.
        // Requires app setting "AzureWebJobsDefaultRoute" value "customPage" to be processed. 
        "customWebsitePath": "/home/site/wwwroot/mywebsite/index.html",
        // Defines the Azure Function which will respond to requests to the default route.
        // Requires app setting "AzureWebJobsDefaultRoute" value "function" to be processed. 
        "functionName": "MyHttpCSharp1"
    }
}

Assumption

An Azure Function needs to have a defined route to use custom routing incl. parameters. If the function gets triggered via the default route on domain root level the response uses default values for parameters which may be defined in the function code (e.g. run.csx in C#).

christopheranderson commented 8 years ago

I love this. I was going to write it myself. I've been annoying @mathewc about it. This falls below our next release bar, but we'll pick it up soon. 👍

mikezks commented 8 years ago

@christopheranderson, great that we share the same passion - feels like in a world of pure imagination. 😃

mikezks commented 7 years ago

Do you have any news on this resp. an estimate by when this feature may be implemented? Thx!

fabiocav commented 7 years ago

@mikezks not yet. There some issues that would need to be addressed, including changes to the infrastructure controllers and other automation that do treat the site root as a non-function endpoint, but we definitely agree that this should be a supported scenario. You'll be able to track any progress as we'll update this issue if/when we have any updates.

cloudkite commented 7 years ago

+1 for this feature!

I would like to propose an alternative to the "defaultRoute" idea as I think it could be supported without adding more configuration options.

Proposal: if you have "routePrefix": "" in your host.json and "route": "" in a function.json then this function would serve the base url. This builds on existing concepts and doesn't require user to learn about additional config options.

This also allows for scenarios such as "route": "{page?}" in function.json

mikezks commented 7 years ago

@cloudkite, good input. My suggestion above has some reasons:

First of all it would already be very helpful to have the possibility to configure custom static websites so that a SPA delivery on domain root level would be possible. This would make a complete severless web app possible within Azure Functions. To use this for production the wake up time needs to decrease clearly.

The entire vision would be to have a completely severless option for all Web App features.

cloudkite commented 7 years ago

@mikezks

Regarding the routePrefix I am with you: this seems to be obsolete already

I think you misunderstood I am not advocating removing this. Currently you add "routePrefix": "" to your host.json if you want a blank prefix instead of the default /api. If combined with a blank "route": "" in a function.json this could then serve as the base url /.

it would already be very helpful to have the possibility to configure custom static websites

If you are developing a SPA you probably want to respond to urls using a function which returns html rather than with static file server. This way you can handle server rendering, dynamic routing and asset hashing. This is why I think "defaultRoute" and "customWebsitePath" config options would hurt rather than help in a SPA use case.

mikezks commented 7 years ago

@cloudkite, I understood what you suggested, but mixed this up in my answer. To describe it right: your post reminded me that I have the opinion that the routePrefix is obsolete now. I think it is better to define the route in one place and not spread over two config files.

Yes, this would be almost the end of the journey as written above. The best way would be having a serverless Web App service.

Nevertheless a first helpful step would be the static website delivery as it is already implemented for the Azure Functions welcome page.

Offering a Function endpoint on default route level would be even more helpful, nevertheless redeveloping serverside features which already exist in the Web App service does only make sense if a severless Web App service will not be on the roadmap within the next months.

mikezks commented 7 years ago

Ad defaultRoute and customWebsitePath: I am not strictly focused on my suggested config in the initial post. This was just meant to describe the whole picture.

Nevertheless configuring the path to a static website directly (and later hopefully rendered on server) would fit to the Azure Functions approach to provide easy to use features. It is far easier to write a path into a config file (or UI supported) than writing a function on your own to answer a HTTP trigger call with a HTML content response. The last mentioned is not really difficult, but it is a perfect fit for an Azure Functions standard feature.

Moreover it would be great to see direct support for JavaScriptServices.

cloudkite commented 7 years ago

To be clear my concern is not with the naming or placement of the config options. I'm concerned with

configuring the path to a static website

IMHO if you want to serve static content you are better off using a CDN. I think functions should be only for dynamic responses, anything outside of this seems like bloat to me.

mikezks commented 7 years ago

Yes, this sums it up. We have different expectations of what Azure Functions shall stand for.

Right now Azure Functions is the only service within Azure to provide serverless execution - technically and commercially. Currently I cannot see any benefit of seperating classic web server features from Web API features.

At least the Azure CDN is no option as you need a Web App and moreover using a CDN may be oversized for several use cases. Blob Storage does not offer an option for domain default route as well.

I do not see any disadvantages in serving static websites.

Edit: Configuring a path to a static website may also lead to automatic processing with JavaScriptServices. This would be a clear distinction compared to a CDN or Blob Storage.

mikezks commented 7 years ago

@cloudkite, your suggestion to allow "route": "{page?}" on domain default route level sounds promising. There is only one question how the prioritization in combination with other Functions shall be handled.

Example: ApiFunc: "route": "orders" DefaultRouteFunc: "route": "{page?}" -> A call to https://myfuncapp.azurewebsites.net/orders must not be routed to DefaultRouteFunc. Would this logic already be supported by the Web API 2 standard routing?

cloudkite commented 7 years ago

@mikezks according to https://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2 in the section on Route ordering, literal segments are always matched first so it should work. However I think they are using a different underlying mechanism from web api

nilayparikh commented 7 years ago

Any update on the default landing page. I'm trying to build a prototype around the stateless website (FaaSW - Function as a Service/Web), I have gathered all designed patterns other than homepage :(

Really appreciate if you pass some update.

Verneri commented 7 years ago

Would be interesting to hear what is the current preffered way to do a website with some backend rendering using function app as it seems that you need one web app as a front and then you can implement the api with functions? Or should you use some gateway loadbalancer always in front of the app to route html rendering to "page" function?

If that is the case it sounds a bit redundant as you could do all of this with functions if the default path could be routed to "render" function.

nilayparikh commented 7 years ago

You don't need any load balancer or other appliance; that's a point. I have tried with LiquidNet, embedded pages in assembly with Azure Storage Table (as backend), results are amazing 35ms ~ 100ms average (only HTML) page rendering.

Scaling the Azure Function is very simple, and there are many preferred ways (depends on what topology you are).

Verneri commented 7 years ago

I was thinking of load balancer as a lightweight heavyduty server capable of URL re routing. Such is needed anycase if the default root web page is always visible and can't be turnee off in an function app

mikezks commented 7 years ago

As mentioned above the Microsoft Dev Team is willing to support default route definition for domain root level, but there is work to do related to the infrastructure before this can be implemented.

What we know now: this issue is not scheduled for January yet.

Verneri commented 7 years ago

Yes i understood that. I was mainly asking what is the preferred way to launch a production spa + api as a function app meanwhile. This boils down to few questions:

How to make the backend rendered web page that loads the rich js app and show it from https://somedomain.something/ root

How to block the azure function app page from showing?

These preferably in the context of js apps and the second one is less important to me right now But in the long run as important as the first one.

To me it looks like web app in front of the function app. Or a loadbalancer/webserver/something that can serve the requests from internal ip's that are blocked from the net.

mikezks commented 7 years ago

@Verneri If you need serverside SPA rendering and domain root access (normally this is the case), then you would currently need to use Web Apps (e.g. with JavaScriptServices).

For the backend you are free to choose form e.g. Web API or Function App (within an App Service plan or as dynamic option). To deactivate the Function App standard welcome page you can set AzureWebJobsDisableHomepage as App Setting. At the moment it would be the easiest option to run your SPA at https://mydomain.tld and the Function App (or any other) backend at https://api.mydomain.tld.

I agree with @nilayparikh that the loadbalancer will not necessarily be needed. In an ideal serverless world we would benefit of loadbalancing which is directly managed by the serverless PaaS - for backend functions as well as for serving (prerendered) (single) webpage applications. Of course also the commercial benefits would be a big step forward.

Verneri commented 7 years ago

Ok, it's as i thought then. One thing though. If disabling homepage from app settings works for function app can't that be used as a way to render the spa server side as well?

nilayparikh commented 7 years ago

@mikezks agree, the major motive behind testing the exercise are "serverless", "stateless", "high volume".

@Verneri if we introduce any additional appliance than it would compromise some important attributes of OOTB service and may introduce other bottlenecks and single point failures.

anthonychu commented 7 years ago

For the "Define a Azure Function which responses to requests on the default route on domain root level" requirement, I agree with @cloudkite that setting routePrefix and route to "" should be all that's needed to trigger a function at the site root. IMO, there's no need for additional host.json settings for this. There's currently a HomeController swallowing up requests at the site root that's preventing this to happen (I think this is the issue that @fabiocav was referring to). Hopefully things can be changed around to enable functions at the site root.

For instance, can the HomeController be removed? Instead, FunctionsController can return the static default page for requests to / if there's no function set up to handle it.

It also would be nice for requests with file extensions to be routed to the function host. Right now it looks like only extensionless routes and ones with the default ASP.NET extensions like .aspx and .ashx can be handled by the function host. IIS is returning 404's for requests with other file extensions


As for the other requirements regarding serving static files, it would be great to see more discussion about how this could be implemented. Also would like to hear from the team about billing implications. (e.g., would each static file request be simply billed as a function execution?)

mikezks commented 7 years ago

@Verneri:

Unfortunately this is not possible the way we want it yet, but the idea also came to my mind as the runtime with the initial Azure Function marketing website launched some months ago.

There are possible workarounds, but none of them satisfy my demand:

Custom Functions runtime as a private site extension for a Classic Function App

It is possible to use a private custom Function runtime. Unfortunately for Classic Function App only, but not for the dynamic plan. So if you use a Function App within an App Service, then you can use your own runtime as site extension. This gives you the ability to change the source as you need it, but be aware that you are responsible for all upgrades to newer releases which may cover bug fixes, new features and breaking changes which may be necessary because of infrastructure changes. See Wiki "Deploying the Functions runtime as a private site extension".

The Home.html which is the source of the initial Azure Functions Marketing website can be found here: WebJobs.Script.WebHost. Unfortunately this website is compiled to Microsoft.Azure.WebJobs.Script.WebHost.dll and both files are not accessible via Kudu. So the only option would be a private runtime as described above. See HomeController.cs if you are interested in how the HTML page is loaded as resource.

Serving a website with a Function

Another option is serving the website with a Function, but this does not work for the default route on domain root level. See Serving an HTML Page from Azure Functions. @anthonychu: I know your post for months, but just recognized that it was you who wrote this. Thanks a lot for sharing! 😃

mikezks commented 7 years ago

@anthonychu:

I guess it was not a good idea to integrate the implementation suggestion into my initial post. I do not like it either to have complex host.json configs, but I had in mind that the currently implemented way has to be continued, because it has a reason why the Microsoft Dev Team implemented it that way. To be honest I have no need for an inital marketing page or an empty page instead of it. So if the new implementation just covers an integrated Function AND web server host with the initial page as static files that can be removed by the Function App admin, then this would be perfectly lean.

But these discussions are details IMO. It would be more important to discuss the general scope:

Concerning static file delivery (not webpages, but the rest) IMO Blob Storage may become a sufficient option, but at the moment there are absolutely not understandable obstacles no one at Microsoft seems to care about for years:

mikezks commented 7 years ago

@anthonychu:

The HomeController does not seem to be an obstacle for this issue. I think @fabiocav did not mean the HomeController as he mentioned work to do concerning "infrastructure controllers and other automation". This sounds to me a bit more complex.

The HomeController was changed as the initial Azure Function marketing website was implemented. Before that the HomeController was just set up to return an empty page: HomeController.cs

Edit: At the moment it takes about 10 seconds to cold start a Function. This will be only one issue that needs to be solved before a productive use as web server would make sense.

anthonychu commented 7 years ago

@mikezks

I think there are 3 fairly distinct topics here so I've divided my thoughts...

Addressing some routing issues

(Correction: We can define a route that matches the root without using a catchall. For instance we can use an optional parameter: {foo?})

I think now that custom routing has been introduced, these remaining issues should be addressed soon to make sure the feature works well. I've taken a stab at the first point with #1102.

Adding out of box support for static files

I agree that the ability to serve static files would make for an interesting feature and blob storage falls short on the points you listed. Sounds like this requires more of a design discussion though. I would imagine the Functions team will lead this discussion when the time comes to implement it.

With slight better routing, there should be nothing stopping us from implementing our own file server in a function as a stop-gap. Currently, the Function app isn't handling requests with static file extensions like .html, so some changes will need to be made to the web.config to make this possible.

JavaScriptServices

This is an interesting idea, but it might be too narrow of a use case. It also only works with .NET Core so it'd have to wait until the Functions runtime can work with it.

anthonychu commented 7 years ago

Found out we can also use optional parameters to define routes that match the root without using a catchall. Updated my previous comment above and the PR #1102.

mikezks commented 7 years ago

As it is an important info for this issue, I add this quote:

https://github.com/Azure/azure-webjobs-sdk-script/pull/1102#issuecomment-272041287 by @fabiocav @anthonychu thanks for the work on this! As I've alluded to here there are some infrastructure changes we'd need to make before we can make this runtime change and, unfortunately, the current runtime behavior, where routing the root path to a function is disabled, is by design (as you've noticed, we had to go a bit out of our way to block that) to prevent issues with those infrastructure components, which would not only impact those components but lead to confusion, risk and charges from unexpected function invocations. Flagging @christopheranderson here for visibility as he would need to help drive this infrastructure work to unblock us. We'll continue the discussion on #848 and reopen this for review once that blocker is resolved.

ricklove commented 7 years ago

@mikezks You mentioned:

At least the Azure CDN is no option as you need a Web App

I want to be able to serve files from a CDN that is backed by my Azure Functions. I was able to serve the files fine using a wildcard route and a Http Function that responds with the correct file.

Then, I was wondering if Azure CDN would work:

So I setup Azure CDN and pointed it to my dynamic azure functions app. It allowed me to configure it fine. However, whenever I try to get any file from the CDN, it always gives me a 404.

One workaround I can imagine is creating a trigger that would push the files to blob storage and use that as the backing for the CDN, but I was hoping there was a way to get it to work without that extra step.

Is there a reason why Azure Functions will not work with Azure CDN? Is there a simple and cheap workaround for serving a few static files for the CDN to distribute?

Update:

Right when I finished this comment, I refreshed the page on my CDN and it worked. It just took some time to get setup I guess. I'm going to test it more to see what is going on. For now here is an example:

CDN: https://told-cdn-endpoint-test.azureedge.net/function-BOILERPLATE/function.json/file SOURCE: https://told-azure-functions-server-test.azurewebsites.net/api/example-function-resource/function-BOILERPLATE/function.json/file

I'm going to test this out more to see if I can get it to work. It seems it is taking a long time for resources to get loaded and 404 is the norm.

Update 2:

Overnight, the CDN is working great now. I just uploaded a new resource and the CDN loaded it on first request. So, Azure CDN works fine with Dynamic Azure Functions.

Examples:

SOURCE: https://told-azure-functions-server-test.azurewebsites.net/api/example-function-resource/sample.jpg/file CDN1: https://told-cdn-endpoint-test.azureedge.net/sample.jpg/file CDN2: https://told-cdn-endpoint2-test.azureedge.net/api/example-function-resource/sample.jpg/file

SOURCE: https://told-azure-functions-server-test.azurewebsites.net/api/example-function-resource/favicon.ico/file CDN1: https://told-cdn-endpoint-test.azureedge.net/favicon.ico/file CDN2: https://told-cdn-endpoint2-test.azureedge.net/api/example-function-resource/favicon.ico/file

Setup:

I created a http function that serves static files with node.js. At first I used a query string to determine the path, but I also configured a wildcard route in the settings that allows me to have no query string:

      // Replace FUNCTION_NAME with actual value
      "route": "FUNCTION_NAME/{*pathName}"

From function.json

{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "route": "FUNCTION_NAME/{*pathName}"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ],
  "disabled": false
}

Known Issue

Azure Functions has a configuration bug that won't allow the wildcard route to end with a filename extension.

https://github.com/Azure/azure-webjobs-sdk-script/issues/969

To workaround it, I added /file to the end of the filename and remove that from the path in my function handler:

let filePath = request.pathName.replace(/\/(file)$/, '');

See: https://github.com/toldsoftware/azure-functions-server/blob/master/src-server/example-function-resource.ts

Conclusion

We can use Azure CDN with Azure Functions.

In fact, if Azure CDN could support a custom domain with SSL today, we could use the CDN to face the web and configure it to allow query strings to pass through.

This would provide us with a solution to run an entire website purely on azure functions. (It wouldn't matter whether Azure Functions has a default response, as the cdn can point to a sub path, but the default response would still simplify routing configuration). We are very close to an ideal situation.

thkrmr commented 7 years ago

@ricklove So https://azure.microsoft.com/en-us/blog/announcing-custom-domain-https-support-with-azure-cdn/ is live now. Albeit apex domains are not supported, hopefully they will be.

mikezks commented 7 years ago

@ricklove sorry, but i missed your updates because i read the email notification of your post only.

This would provide us with a solution to run an entire website purely on azure functions. (It wouldn't matter whether Azure Functions has a default response, as the cdn can point to a sub path, but the default response would still simplify routing configuration). We are very close to an ideal situation.

this sounds promising indeed - thanks for sharing! just one question: did you find a solution to directly point from the TLD to the function app via CDN? e.g. http://www.mywebsite.com to http://myfuntion.azurewebsites.net/api/filefunc/index.html/file?

@thkrmr great that this works now!

ricklove commented 7 years ago

@mikezks Yes, I have a default function to handle the root path:

I point the CDN to the default /api/. Then I have it configured with a wildcard route so it essentially is a catch all, but I use a route filter with maximum length 0 so it actually only matches the blank route.

{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "route": "{default:maxlength(0)?}"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ],
  "disabled": false
}
mikezks commented 7 years ago

@ricklove, thanks! great, this is a big step forward.

now two things are left to solve so that it makes sense as production use case:

ricklove commented 7 years ago

@thkrmr Wow, not only is it possible, Microsoft provides the certs for free with no management!

Not having to manage SSL certs: That's nice!

So now all that is needed is for Azure Functions to allow file extensions in the final route parameter (#969) and it will be entirely possible to host an entire website without any user-visible hacks:

@mikezks

Instead of using Javascript Services running on top of ASP.Net, it might be possible to spin up a headless browser running in node.js and have it do the server-side rendering. The nice thing about that is that it would work for any server-side tech.

I've thought about the extreme case of that which is what Opera Mini did once: they would have a server-side browser acting as a proxy on behalf of the client. (http://bloggingexperiment.com/server-side-rendering-opera-mini-5).

mikezks commented 7 years ago

@ricklove, BTW - at least phantomJS is no option ATM: https://github.com/Azure/azure-webjobs-sdk-script/issues/988

ricklove commented 7 years ago

@mikezks Ah, too bad. I wonder if there is an engine that only does DOM manipulation without graphics support. I found this:

https://www.reddit.com/r/javascript/comments/2qfhug/serverside_dom_manipulation/

Which linked to this:

https://github.com/tmpvar/jsdom

It seems like alot of work though.

mikezks commented 7 years ago

@ricklove, thx i will try this later. maybe pre-rendering (not during runtime) is an option for some use cases.

  • reduce wake-up time -> maybe this works with a second function in the same function app with a time-based trigger, so that the function app does not shut down.

i tried this and it seems to work. a time trigger function in the same function app which executes every five minutes, keeps the whole function app awake and the response time is normal (means good).

ricklove commented 7 years ago

I just ran into Azure Function Proxies:

https://docs.microsoft.com/en-us/azure/azure-functions/functions-proxies

This opens up many interesting scenarios. It looks like it is possible to define things at the root level, and hopefully it will handle . (dot) in the last path segment (which would allow static file hosting).

Anybody tested this yet?

mikezks commented 7 years ago

@ricklove: WOOHOO, yes the root level access works now! proxy.json

{
    "proxies": {
        "Proxy1": {
            "matchCondition": {
                "route": "*"
            },
            "backendUri": "https://<funcapp>.azurewebsites.net/function1"
        }
    }
}

thx for the info - i haven't noticed this feature until your post.

mikezks commented 7 years ago

the dot issue in the last path segment is not resolved yet.

mikezks commented 7 years ago

@christopheranderson: now the aimed target is almost reached - the last remaining issue is https://github.com/Azure/azure-webjobs-sdk-script/issues/969. as soon as file extensions (exactly means the dot) are routed correctly, it will be possible to serve a website.

thx a lot to all of you for the proxy feature, which was a very important missing part!

ricklove commented 7 years ago

Yes, it works now! #969

(At least with proxies)

A cutting edge website can now be built with a single consumption plan azure function app:

Bonus (Global Web Api):

If desired, it might be possible to have multiple api endpoints across the globe (I haven't tested this):

Now when a client uses the query string to hit the web api, it will pass through the Azure CDN and route through the Azure Traffic Manager to the nearest Azure Functions App.

Of course your distributed azure function apps will have to use storage queues or something to push their data back to your main region, but your clients are going to have the fastest website and web api no matter where they are in the world.

This is great.

Once, I test this, I will be moving our future production to this architecture.

mikezks commented 7 years ago

IMO the core topics of this issue are resolved now. I suggest to sum up any additional issues connected with this one and to open new ones if there is need for it. Thanks a lot to all of you who contributed actively here.

mikezks commented 7 years ago

Be aware that the Azure Functions Proxies routing is not as complete as for Web API 2 or Azure Functions: e.g. {id:int} will not be routed correctly.

faultylee commented 7 years ago

@mikezks I'm trying to serve a static page on the root, but I've only managed to get 204. Is there anything else I need to configure other than AzureWebJobsDisableHomepage and this

        "landingpage": {
            "matchCondition": {
                "route": "*"
            },
            "backendUri": "https://myfunction.azurewebsites.net/api/StaticFileServer?file=index.html"
        }

I'm not too familiar with Routing like in Web API 2 not sure if that's what I'm missing.

securityvoid commented 6 years ago

@faultylee I'm having the same issue. Was there a change in the way Proxies work?

I can successfully disable the default homepage, but I can't seem to get a Proxy to pickup the default root path.

@ricklove Did you have to make any changes recently to keep this working?

faultylee commented 6 years ago

@securityvoid not sure, I didn't manage to have it working, so I left it aside for now

ltouro commented 6 years ago

@securityvoid @faultylee I was able to make it work. I did not needed any functions actually, just redirect alternative domains to main CDN. but I think you can adapt to use a function backend:

  1. Set AzureWebJobsDisableHomepage to true
  2. Create one proxy for requests with path. The template is "{restOfPath}". I overwrite the response with status 302 and location = https://mydest.tld/{restOfPath} .
  3. Create another proxy for root requests, with the template "/". Did the redirect to on the same fashion above but with no path.