# [Serve]

> Gives a function an address on the app's own origin. A GET at that address answers with the function's return value as the whole response body — a `string` as UTF-8 text, a `byte[]` as its bytes — with a Content-Type inferred from the address or named. It is how an app publishes `/sitemap.xml`, `/robots.txt`, a feed, or anything else a machine fetches by a well-known address.

<!-- id: api-serve · area: api · stability: stable · html: https://osysharp.com/reference/api/serve/ -->

## Summary        {#summary}
Some things an app publishes are not pages. A search engine reads `/sitemap.xml` and `/robots.txt`, a feed reader
reads a feed, and each of them fetches a **fixed address** on the app's own host. `[Serve]` gives a function one of
those addresses. The function reads the app's data like any other and returns the body.

```osy syntax
[Serve("/robots.txt")]
[AllowAnonymous]
string Robots() => "User-agent: *\nSitemap: https://shop.example.com/sitemap.xml\n";
```

## Signature      {#signature}
```osy syntax
[Serve("<path>")]                                   // Content-Type inferred from the path's extension
[Serve("<path>", ContentType = "<media type>")]     // …or named
string  Name(<one parameter per {segment}>) { … }   // the body, served as UTF-8 text
byte[]  Name(<one parameter per {segment}>) { … }   // the body, served as those bytes
```

## Description    {#description}

### The address   {#address}
The path is on the app's own origin — one leading `/`, no query, no fragment. A `{name}` segment binds the function
parameter called `name`, exactly as a page's route does, and every parameter has to be one: a GET has nothing else to
give it. A segment is a whole parameter or none, so `/feeds/{slug}` is fine and `/feeds/{slug}.xml` is refused.

Two things may not share an address, and the compiler says which ones collided: two served functions, or a served
function and a page. Addresses the platform answers on every app are refused, because the function would never be
reached: `/api/…`, `/_osy/…`, `/oauth/…`, `/zone/…`, `/.well-known/oauth-…`, `/health`, and the favicon addresses,
which are the app's icon.

A path ending in an extension, like `/sitemap.xml`, is the ordinary case, and it works for pages too: a page route
whose parameter holds a dot (`/p/v1.2`) is served the page.

### The body and its Content-Type   {#body}
The function returns the **whole body**: a `string` is served as UTF-8 text and a `byte[]` as its bytes. Any other
return type is refused, because a served address has no envelope around it — what the function returns is exactly
what arrives.

Without `ContentType`, the type follows the extension. `.xml` is `application/xml`, `.txt` is `text/plain`, `.json`
is `application/json`, and there are also `.webmanifest`, `.csv`, `.rss`, `.atom`, `.ics`, `.md`, `.svg`, `.png`,
`.jpg`, `.webp`, `.gif`, `.ico` and `.pdf`. Text types carry `charset=utf-8`. An address with no known extension is
`text/plain` for a string and `application/octet-stream` for bytes.

`text/html` is refused: an HTML document is a page, and a page is a component with `[Route("/…")]`, which gets the
app's [head](https://osysharp.com/reference/ui/head/), theme and security for free.

### Who it runs as   {#caller}
It runs as the **caller**. A crawler is always signed out, so a function a crawler must reach is
`[AllowAnonymous]`, like any function reachable without a sign-in. Without it, a signed-out caller is answered
`401` and a signed-in one runs it as themselves. Either way it reads only what that caller may read, because the
function's data reads are ordinary reads.

A declared refusal inside the function maps to its status: a denial is `403`, not-found is `404`, a validation
refusal is `400`. A fault is `500`, with a reference to the log line and nothing else.

### Caching   {#caching}
Every response carries an `ETag` computed from its body, and `Cache-Control: no-cache`. A client or crawler that
sends the ETag back is answered `304 Not Modified` without the body when nothing changed. So a sitemap can be read
as often as a crawler likes, and it is always current.

## Examples       {#examples}

### A sitemap from the app's own rows   {#example-sitemap}
```osy title="every product page, listed where a search engine looks for it" test app=api-serve
entity Product {
  [MaxLength(200)] string Slug;
  security { allow read when IsAnonymous || IsAuthenticated; }
}

[Serve("/sitemap.xml")]
[AllowAnonymous]
string Sitemap() {
  var xml = "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">";
  foreach (var p in Product.OrderBy(p => p.Slug).ToList()) {
    xml = xml + "<url><loc>https://shop.example.com/p/" + p.Slug + "</loc></url>";
  }
  return xml + "</urlset>";
}

[Serve("/robots.txt")]
[AllowAnonymous]
string Robots() { return "User-agent: *\nSitemap: https://shop.example.com/sitemap.xml\n"; }

[Route("/p/{slug}")]
[AllowAnonymous]
[Title("Product")]
component ProductPage(string slug) { render { Text(slug); } }
```

### An address with a parameter, and bytes   {#example-param}
```osy title="a per-product JSON feed, and an image made of bytes" test app=api-serve-param
[Serve("/feeds/{slug}", ContentType = "application/json")]
[AllowAnonymous]
string Feed(string slug) { return "{\"slug\":\"" + slug + "\"}"; }

[Serve("/pixel.png")]
[AllowAnonymous]
byte[] Pixel() {
  return Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=");
}
```

### What the compiler refuses   {#example-refused}
```osy syntax
[Serve("/api/export")]   string A() { … }                   // an address the platform answers on every app
[Serve("/p/{id}")]       string B(string id) { … }          // the page 'ProductPage' is routed at "/p/{slug}"
[Serve("/count")]        int C() { … }                      // a served function returns `string` or `byte[]`
[Serve("/m/{a}")]        string D(string b) { … }           // `{a}` binds nothing, and `b` has no segment
[Serve("/h", ContentType = "text/html")] string E() { … }   // an HTML document is a PAGE
```

## See also       {#see-also}
- [head](https://osysharp.com/reference/ui/head/): a page's own title, description, canonical address and structured data, and its 301.
- [publishing a REST API (app.Apis)](https://osysharp.com/reference/api/rest/): publishing functions and entities as a JSON API for another program.
- [routes and pages](https://osysharp.com/reference/ui/routing/): how a page gets its address.
- [moved addresses (app.Redirects)](https://osysharp.com/reference/config/redirects/): `app.Redirects`, for an address that moved.
- [byte[]](https://osysharp.com/reference/types/byte-array/): the `byte[]` a served function returns for binary content.
