Implementatie van web-API

Een zorgvuldig ontworpen RESTful-web-API definieert de resources, relaties en navigatieschema's die toegankelijk zijn voor clienttoepassingen. Wanneer u een web-API inzet en implementeert, moet u rekening houden met de fysieke vereisten van de omgeving die als host fungeert voor de web-API, en de manier waarop de web-API is samengesteld, in plaats van de logische structuur van de gegevens. Deze richtlijnen zijn gericht op best practices voor het implementeren van een web-API en het publiceren ervan om deze beschikbaar te maken voor clienttoepassingen. Zie Web-API-ontwerp voor gedetailleerde informatie over web-API-ontwerp.

Het verwerken van aanvragen

Houd rekening met de volgende punten wanneer u de code voor het verwerken van aanvragen implementeert.

De acties GET, PUT, DELETE, HEAD en PATCH moeten idempotent zijn

De code die deze aanvragen implementeert, moet geen bijwerkingen veroorzaken. Dezelfde aanvraag die via dezelfde resource wordt herhaald, moet resulteren in dezelfde staat. Meerdere DELETE-aanvragen naar dezelfde URI moeten bijvoorbeeld hetzelfde effect hebben, hoewel de HTTP-statuscode in de antwoordberichten mogelijk anders is. De eerste DELETE-aanvraag retourneert mogelijk statuscode 204 (Geen inhoud), terwijl een volgende DELETE-aanvraag misschien statuscode 404 (Niet gevonden) retourneert.

Notitie

Het artikel Idempotency Patterns op de blog van Jonathan Oliver biedt een overzicht van idempotentie en hoe dit zich verhoudt tot gegevensbeheerbewerkingen.

POST-acties die nieuwe resources maken moeten geen ongerelateerde neveneffecten hebben

Als een POST-aanvraag is bedoeld om een nieuwe resource te maken, moeten de effecten van de aanvraag worden beperkt tot de nieuwe resource (en mogelijk eventuele direct gerelateerde resources als er een soort koppeling bij betrokken is). In een e-commercesysteem kan bijvoorbeeld een POST-aanvraag die een nieuwe order voor een klant maakt, ook voorraadniveaus wijzigen en factureringsgegevens genereren. maar het mag geen informatie wijzigen die niet rechtstreeks verband houdt met de bestelling of andere neveneffecten hebben op de algehele status van het systeem.

Vermijd het implementeren van veeleisende POST-, PUT- en DELETE-bewerkingen

Ondersteun POST-, PUT- en DELETE-aanvragen via de resource-verzamelingen. Een POST-aanvraag kan de details voor meerdere nieuwe resources bevatten en ze alle toevoegen aan dezelfde verzameling, een PUT-aanvraag kan de volledige set van resources in een verzameling vervangen en een DELETE-aanvraag kan een hele verzameling verwijderen.

De OData-ondersteuning die is opgenomen in ASP.NET Web API 2, biedt de mogelijkheid tot batchaanvragen. Een clienttoepassing kan verschillende web-API-aanvragen verpakken en ze verzenden naar de server in één HTTP-aanvraag en één HTTP-antwoord met de antwoorden op elke aanvraag. Zie Introductie van batchondersteuning in web-API en web-API OData voor meer informatie.

Volg de HTTP-specificatie bij het verzenden van een reactie

Een web-API moet berichten retourneren met de juiste HTTP-statuscode, zodat de client kan bepalen hoe hij het resultaat moet afhandelen, met de juiste HTTP-headers, zodat de client de aard van het resultaat begrijpt, en een naar behoren opgemaakte hoofdtekst zodat de client het resultaat kan parseren.

Een POST-bewerking moet bijvoorbeeld statuscode 201 (Gemaakt) retourneren en in het antwoordbericht moet de URI van de zojuist gemaakte resource in de koptekst van de locatie van het antwoordbericht worden opgenomen.

Ondersteuning voor inhoudsonderhandeling

De hoofdtekst van een antwoordbericht bevat mogelijk gegevens in verschillende indelingen. Een HTTP GET-aanvraag kan bijvoorbeeld gegevens retourneren in de JSON, of de XML-indeling. Wanneer de client een aanvraag verzendt, kan hierin een Accept-header worden opgenomen, die de gegevensopmaak aangeeft die kan worden verwerkt. Deze indelingen worden opgegeven als mediatypen. Een client die bijvoorbeeld een GET-aanvraag uitgeeft waarmee een afbeelding wordt opgehaald, kan een Accept-header opgeven met de mediatypen die de client kan verwerken, zoals image/jpeg, image/gif, image/png. Wanneer de web-API het resultaat retourneert, moet het de gegevens formatteren met behulp van een van de volgende mediatypen en de indeling opgeven in de Content-Type-header van het antwoord.

Als de client geen Accept-header opgeeft, gebruikt u een zinnige standaardindeling voor de hoofdtekst van het antwoord. Zo valt het framework ASP.NET Web API standaard terug op JSON voor gegevens op basis van tekst.

De HATEOAS-aanpak maakt het voor clients mogelijk om te navigeren en resources te detecteren vanuit een initieel beginpunt. Dit wordt bereikt door middel van koppelingen met URI's. Wanneer een client een HTTP GET-aanvraag uitgeeft om een resource te verkrijgen, moet het antwoord URI's bevatten waarmee een clienttoepassing snel direct gerelateerde resources kan vinden. In een web-API die ondersteuning biedt voor een e-commerce-oplossing, kan een klant bijvoorbeeld veel orders hebben geplaatst. Wanneer een clienttoepassing de details voor een klant ophaalt, moet het antwoord koppelingen bevatten die het de clienttoepassing mogelijk maken om HTTP GET-aanvragen te verzenden die deze orders kunnen ophalen. Bovendien moeten links in HATEOAS-stijl de andere bewerkingen omschrijven (POST, PUT, DELETE, enzovoort) die elke gekoppelde resource ondersteunt, samen met de overeenkomstige URI om elke aanvraag uit te voeren. Deze aanpak wordt uitgebreider beschreven in API-ontwerp.

Er zijn momenteel geen normen die de uitvoering van HATEOAS bepalen, maar het volgende voorbeeld geeft een mogelijke aanpak weer. In dit voorbeeld retourneert een HTTP GET-aanvraag die de details voor een klant zoekt een antwoord met HATEOAS-koppelingen die verwijzen naar de orders voor die klant:

GET https://adventure-works.com/customers/2 HTTP/1.1
Accept: text/json
...
HTTP/1.1 200 OK
...
Content-Type: application/json; charset=utf-8
...
Content-Length: ...
{"CustomerID":2,"CustomerName":"Bert","Links":[
    {"rel":"self",
    "href":"https://adventure-works.com/customers/2",
    "action":"GET",
    "types":["text/xml","application/json"]},
    {"rel":"self",
    "href":"https://adventure-works.com/customers/2",
    "action":"PUT",
    "types":["application/x-www-form-urlencoded"]},
    {"rel":"self",
    "href":"https://adventure-works.com/customers/2",
    "action":"DELETE",
    "types":[]},
    {"rel":"orders",
    "href":"https://adventure-works.com/customers/2/orders",
    "action":"GET",
    "types":["text/xml","application/json"]},
    {"rel":"orders",
    "href":"https://adventure-works.com/customers/2/orders",
    "action":"POST",
    "types":["application/x-www-form-urlencoded"]}
]}

In dit voorbeeld worden de klantgegevens vertegenwoordigd door de Customer-klasse die wordt weergegeven in het volgende codefragment. De HATEOAS-links worden bewaard in de verzamelingseigenschap Links:

public class Customer
{
    public int CustomerID { get; set; }
    public string CustomerName { get; set; }
    public List<Link> Links { get; set; }
    ...
}

public class Link
{
    public string Rel { get; set; }
    public string Href { get; set; }
    public string Action { get; set; }
    public string [] Types { get; set; }
}

De HTTP GET-bewerking haalt de klantgegevens op uit de opslag en maakt een Customer-object en vult vervolgens de Links-verzameling. Het resultaat is opgemaakt als een JSON-antwoordbericht. Elke koppeling omvat de volgende velden:

  • De relatie tussen het object dat wordt geretourneerd en het object dat wordt beschreven door de link. In dit geval self geeft aan dat de koppeling een verwijzing naar het object zelf is (vergelijkbaar met een this aanwijzer in veel objectgeoriënteerde talen) en orders de naam is van een verzameling die de gerelateerde volgordegegevens bevat.
  • De hyperlink (Href) voor het object die wordt beschreven door de link in de vorm van een URI.
  • Het type HTTP-aanvraag (Action) dat naar deze URI kan worden verzonden.
  • De indeling van gegevens (Types) die moet worden opgegeven in de HTTP-aanvraag of die in het antwoord kan worden geretourneerd, afhankelijk van het type van de aanvraag.

De in het voorbeeld-HTTP-antwoord weergegeven HATEOAS-links geven aan dat een clienttoepassing de volgende bewerkingen kan uitvoeren:

  • Een HTTP GET-aanvraag naar de URI https://adventure-works.com/customers/2 voor het ophalen van de details van de klant (opnieuw). De gegevens kunnen worden geretourneerd als XML of JSON.
  • Een HTTP PUT-aanvraag naar de URI https://adventure-works.com/customers/2 voor het wijzigen van de details van de klant. De nieuwe gegevens moeten worden opgegeven in het aanvraagbericht in de indeling x-www-form-urlencoded.
  • Een HTTP DELETE-aanvraag naar de URI https://adventure-works.com/customers/2 voor het verwijderen van de klant. De aanvraag verwacht geen eventuele aanvullende informatie en retourneert geen gegevens in de hoofdtekst van het antwoord.
  • Een HTTP GET-aanvraag naar de URI https://adventure-works.com/customers/2/orders om te zoeken naar alle orders voor de klant. De gegevens kunnen worden geretourneerd als XML of JSON.
  • Een HTTP POST-aanvraag naar de URI https://adventure-works.com/customers/2/orders om een nieuwe order voor deze klant te maken. De gegevens moeten worden opgegeven in het aanvraagbericht in de indeling x-www-form-urlencoded.

Afhandeling van uitzonderingen

Houd rekening met de volgende punten als een bewerking een niet-onderschepte uitzondering genereert.

Vastleggen van uitzonderingen en relevante antwoorden aan clients geven

De code die een HTTP-bewerking implementeert, moet uitgebreide uitzonderingen kunnen verwerken en geen niet-onderschepte uitzonderingen laten doorgaan naar het framework. Als een uitzondering het onmogelijk maakt om de bewerking te voltooien, kan de uitzondering weer in het antwoordbericht worden doorgegeven, maar hierbij moet een duidelijke beschrijving bijgevoegd zijn van de fout die de uitzondering heeft veroorzaakt. De uitzondering moet ook de juiste HTTP-statuscode bevatten in plaats van gewoon de statuscode 500 voor elke situatie te retourneren. Als een gebruikersaanvraag bijvoorbeeld zorgt voor een database-update die in strijd is met een beperking (zoals bij het verwijderen van een klant die openstaande orders heeft), moet u bijvoorbeeld de statuscode 409 (Conflict) retourneren met een berichttekst die de reden voor het conflict aangeeft. Als een bepaalde andere voorwaarde ervoor zorgt dat niet aan de aanvraag voldaan kan worden, kunt u statuscode 400 (Ongeldige aanvraag) retourneren. U vindt een volledige lijst met HTTP-statuscodes op de pagina Statuscodedefinities op de W3C-website.

Het codevoorbeeld onderschept verschillende voorwaarden en retourneert de juiste reactie.

[HttpDelete]
[Route("customers/{id:int}")]
public IHttpActionResult DeleteCustomer(int id)
{
    try
    {
        // Find the customer to be deleted in the repository
        var customerToDelete = repository.GetCustomer(id);

        // If there is no such customer, return an error response
        // with status code 404 (Not Found)
        if (customerToDelete == null)
        {
            return NotFound();
        }

        // Remove the customer from the repository
        // The DeleteCustomer method returns true if the customer
        // was successfully deleted
        if (repository.DeleteCustomer(id))
        {
            // Return a response message with status code 204 (No Content)
            // To indicate that the operation was successful
            return StatusCode(HttpStatusCode.NoContent);
        }
        else
        {
            // Otherwise return a 400 (Bad Request) error response
            return BadRequest(Strings.CustomerNotDeleted);
        }
    }
    catch
    {
        // If an uncaught exception occurs, return an error response
        // with status code 500 (Internal Server Error)
        return InternalServerError();
    }
}

Tip

Neem geen informatie op die nuttig kan zijn voor een aanvaller die uw API probeert te binnendringen.

Veel webservers onderscheppen zelf foutvoorwaarden voordat deze de web-API bereiken. Als u bijvoorbeeld de verificatie voor een website configureert en de gebruiker niet de juiste verificatie-informatie kan verstrekken, moet de webserver reageren met de statuscode 401 (Niet-gemachtigd). Zodra een client is geverifieerd, kan uw code zijn eigen controles uitvoeren om te controleren of de client toegang moet krijgen tot de aangevraagde resource. Als deze verificatie is mislukt, moet u de statuscode 403 (Verboden) retourneren.

Wees consistent met het verwerken van uitzonderingen en registreer informatie over fouten

Overweeg de implementatie van een strategie voor een globale foutafhandeling over de hele web-API voor het afhandelen van uitzonderingen op een consistente manier. U moet ook een foutenlogboek opnemen die de volledige details van elke uitzondering vastlegt. Dit foutenlogboek kan gedetailleerde informatie bevatten, zolang deze niet via het web naar clients toegankelijk is gemaakt.

Onderscheid maken tussen fouten van client-zijde en serverzijde

Het HTTP-protocol maakt onderscheid tussen de fouten die optreden door de clienttoepassing (de HTTP 4xx-statuscodes) en fouten die worden veroorzaakt door problemen op de server (HTTP 5xx-statuscodes). Zorg ervoor dat u deze afspraak naleeft in alle eventuele foutberichten.

Toegang tot gegevens van clientzijde optimaliseren

In een gedistribueerde omgeving, zoals met een webserver en clienttoepassingen, is het netwerk een van de belangrijkste bronnen van zorg. Dit kan fungeren als een aanzienlijk knelpunt, vooral als een clienttoepassing vaak aanvragen verzendt of gegevens ontvangt. U moet zich er daarom op richten de hoeveelheid verkeer die via het netwerk loopt te minimaliseren. Houd rekening met de volgende punten wanneer u de code implementeert om gegevens op te halen en te behouden:

Ondersteuning voor caching aan clientzijde

Het HTTP 1.1-protocol ondersteunt opslaan in cache in clients en op tussenliggende servers waarmee een aanvraag wordt gerouteerd, door het gebruik van de Cache-Control-header. Wanneer een clienttoepassing een HTTP GET-aanvraag naar de web-API verzendt, kan het antwoord een Cache-Control-header bevatten die aangeeft of de gegevens in de hoofdtekst van het antwoord veilig kunnen worden opgeslagen door de client, of een tussenliggende server waarover de aanvraag wordt gerouteerd, en hoelang het duurt voordat deze moet verlopen en als verouderd kan worden beschouwd.

In het volgende voorbeeld ziet u een HTTP GET-aanvraag en de desbetreffende reactie die een Cache-Control-header bevat:

GET https://adventure-works.com/orders/2 HTTP/1.1
HTTP/1.1 200 OK
...
Cache-Control: max-age=600, private
Content-Type: text/json; charset=utf-8
Content-Length: ...
{"orderID":2,"productID":4,"quantity":2,"orderValue":10.00}

In dit voorbeeld geeft de Cache-Control-header aan dat de geretourneerde gegevens na 600 seconden moeten vervallen en alleen geschikt zijn voor één client. Ze moeten niet worden opgeslagen in een gedeelde cache die wordt gebruikt door andere clients (het is persoonlijk). De Cache-Control-header kan Openbaar retourneren in plaats van Persoonlijk. In dat geval kunnen de gegevens worden opgeslagen in een gedeelde cache of er kan worden opgegeven Niet Opslaan. In dat geval moeten de gegevens niet in de cache worden opgeslagen door de client. Het volgende codevoorbeeld toont het maken van een Cache-Control-header in een antwoordbericht:

public class OrdersController : ApiController
{
    ...
    [Route("api/orders/{id:int:min(0)}")]
    [HttpGet]
    public IHttpActionResult FindOrderByID(int id)
    {
        // Find the matching order
        Order order = ...;
        ...
        // Create a Cache-Control header for the response
        var cacheControlHeader = new CacheControlHeaderValue();
        cacheControlHeader.Private = true;
        cacheControlHeader.MaxAge = new TimeSpan(0, 10, 0);
        ...

        // Return a response message containing the order and the cache control header
        OkResultWithCaching<Order> response = new OkResultWithCaching<Order>(order, this)
        {
            CacheControlHeader = cacheControlHeader
        };
        return response;
    }
    ...
}

Deze code maakt gebruik van een aangepaste IHttpActionResult klasse met de naam OkResultWithCaching. Deze klasse maakt gebruik van de controller voor het instellen van de inhoud van de cache-header:

public class OkResultWithCaching<T> : OkNegotiatedContentResult<T>
{
    public OkResultWithCaching(T content, ApiController controller)
        : base(content, controller) { }

    public OkResultWithCaching(T content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
        : base(content, contentNegotiator, request, formatters) { }

    public CacheControlHeaderValue CacheControlHeader { get; set; }
    public EntityTagHeaderValue ETag { get; set; }

    public override async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response;
        try
        {
            response = await base.ExecuteAsync(cancellationToken);
            response.Headers.CacheControl = this.CacheControlHeader;
            response.Headers.ETag = ETag;
        }
        catch (OperationCanceledException)
        {
            response = new HttpResponseMessage(HttpStatusCode.Conflict) {ReasonPhrase = "Operation was cancelled"};
        }
        return response;
    }
}

Notitie

Het HTTP-protocol bepaalt ook de no-cache-richtlijn voor de Cache-Control-header. Het is nogal verwarrend dat deze richtlijn niet betekent 'niet opslaan in cache' maar in plaats daarvan 'valideer de gegevens in cache met de server opnieuw, voordat deze worden teruggezonden'. De gegevens kunnen nog steeds in de cache worden opgeslagen, maar ze worden telkens wanneer ze worden gebruikt gecontroleerd, om ervoor te zorgen dat ze nog steeds actueel zijn.

Cachebeheer is de verantwoordelijkheid van de clienttoepassing of tussenliggende server, maar als het op de juiste wijze is geïmplementeerd, kunt u bandbreedte besparen en de prestaties verbeteren, door het verwijderen van de noodzaak tot ophalen van gegevens die onlangs al zijn opgehaald.

De max-age-waarde in de Cache-Control-header is alleen een richtlijn en geen garantie dat de bijbehorende gegevens niet worden gewijzigd tijdens de opgegeven periode. De web-API moet de max-age instellen op een geschikte waarde, afhankelijk van de verwachte volatiliteit van de gegevens. Wanneer deze periode is verlopen, moet de client het object uit de cache verwijderen.

Notitie

De meeste moderne webbrowsers bieden ondersteuning voor caching aan clientzijde door de juiste cache-control-headers toe te voegen aan aanvragen en de headers van de resultaten te onderzoeken, zoals is omschreven. Sommige oudere browsers slaan echter geen waarden op in de cache, als deze zijn geretourneerd door een URL die een queryreeks bevat. Dit is meestal geen probleem voor clienttoepassingen, die hun eigen strategie voor het beheer van cache op basis van het hier besproken protocol implementeren.

Sommige oudere proxy's vertonen hetzelfde gedrag en slaan mogelijk geen aanvragen op basis van URL's met querytekenreeksen op in de cache. Dit is mogelijk een probleem voor aangepaste clienttoepassingen die verbinding met een webserver via deze proxy maken.

Bied ETags voor het optimaliseren van het verwerken van query's

Wanneer een clienttoepassing een object ophaalt, kan het antwoordbericht ook een ETag (entiteitscode) bevatten. Een ETag is een ondoorzichtige tekenreeks die de versie van een resource aangeeft; telkens wanneer een resource verandert, wordt de ETag ook gewijzigd. Deze ETag mag als onderdeel van de gegevens worden opgeslagen door de clienttoepassing. De volgende voorbeeldcode laat zien hoe een ETag kan worden toegevoegd als onderdeel van het antwoord op een HTTP GET-aanvraag. Deze code gebruikt de GetHashCode-methode van een object voor het genereren van een numerieke waarde die het object identificeert (u kunt deze methode indien nodig overschrijven en uw eigen hash genereren met behulp van een algoritme zoals MD5):

public class OrdersController : ApiController
{
    ...
    public IHttpActionResult FindOrderByID(int id)
    {
        // Find the matching order
        Order order = ...;
        ...

        var hashedOrder = order.GetHashCode();
        string hashedOrderEtag = $"\"{hashedOrder}\"";
        var eTag = new EntityTagHeaderValue(hashedOrderEtag);

        // Return a response message containing the order and the cache control header
        OkResultWithCaching<Order> response = new OkResultWithCaching<Order>(order, this)
        {
            ...,
            ETag = eTag
        };
        return response;
    }
    ...
}

Het antwoordbericht dat door de web-API wordt gepost, ziet er als volgt uit:

HTTP/1.1 200 OK
...
Cache-Control: max-age=600, private
Content-Type: text/json; charset=utf-8
ETag: "2147483648"
Content-Length: ...
{"orderID":2,"productID":4,"quantity":2,"orderValue":10.00}

Tip

Sta om veiligheidsredenen niet toe dat gevoelige gegevens of gegevens die worden geretourneerd via een geverifieerde (HTTPS)-verbinding, in de cache worden opgeslagen.

Een clienttoepassing kan ook een volgende GET-aanvraag doen om dezelfde resource op elk gewenst moment op te halen, en als de resource is gewijzigd (deze heeft een andere ETag), moet de versie in de cache worden verwijderd en de nieuwe versie worden toegevoegd aan het cachegeheugen. Als een bron groot is en een aanzienlijke hoeveelheid bandbreedte vereist om terug naar de client te kunnen zenden, kunnen herhaalde aanvragen voor het ophalen van dezelfde gegevens inefficiënt zijn. Om dit te bestrijden, definieert het HTTP-protocol het volgende proces voor het optimaliseren van GET-aanvragen die u in een web-API moet ondersteunen:

  • De client bouwt een GET-aanvraag die de ETag bevat voor de versie die momenteel in de cache van de resource is opgeslagen, waarnaar wordt verwezen in een If-None-Match HTTP-header:

    GET https://adventure-works.com/orders/2 HTTP/1.1
    If-None-Match: "2147483648"
    
  • De GET-bewerking in de web-API verkrijgt de huidige ETag voor de aangevraagde gegevens (volgorde 2 in het bovenstaande voorbeeld) en vergelijkt deze met de waarde in de If-None-Match-header.

  • Als de huidige ETag voor de aangevraagde gegevens overeenkomt met de ETag die is geleverd door de aanvraag, is de bron niet gewijzigd en moet de web-API een HTTP-antwoord met een lege berichttekst en statuscode 304 (Niet gewijzigd) retourneren.

  • Als de huidige ETag voor de aangevraagde gegevens niet overeenkomt met de ETag die is geleverd door de aanvraag, zijn de gegevens gewijzigd en moet de web-API een HTTP-antwoord met een lege berichttekst en statuscode 200 (OK) retourneren.

  • Als de aangevraagde gegevens niet meer bestaan, moet de web-API een HTTP-antwoord met de statuscode 404 (Niet gevonden) retourneren.

  • De client gebruikt de statuscode voor het onderhouden van de cache. Als de gegevens niet zijn gewijzigd (statuscode 304), kan het object in de cache opgeslagen blijven en moet de clienttoepassing doorgaan met het gebruik van deze versie van het object. Als de gegevens zijn gewijzigd (statuscode 200), moet het object in de cache worden verwijderd en het nieuwe worden ingevoegd. Als de gegevens niet meer beschikbaar zijn (statuscode 404), moet het object worden verwijderd uit de cache.

Notitie

Als de antwoord-header de Cache-Control-header no-store bevat, moet het object altijd worden verwijderd uit de cache, ongeacht de HTTP-statuscode.

De code hieronder toont de FindOrderByID-methode, uitgebreid ter ondersteuning van de If-None-Match-header. U ziet dat de opgegeven order altijd wordt opgehaald als de If-None-Match-header wordt weggelaten:

public class OrdersController : ApiController
{
    [Route("api/orders/{id:int:min(0)}")]
    [HttpGet]
    public IHttpActionResult FindOrderByID(int id)
    {
        try
        {
            // Find the matching order
            Order order = ...;

            // If there is no such order then return NotFound
            if (order == null)
            {
                return NotFound();
            }

            // Generate the ETag for the order
            var hashedOrder = order.GetHashCode();
            string hashedOrderEtag = $"\"{hashedOrder}\"";

            // Create the Cache-Control and ETag headers for the response
            IHttpActionResult response;
            var cacheControlHeader = new CacheControlHeaderValue();
            cacheControlHeader.Public = true;
            cacheControlHeader.MaxAge = new TimeSpan(0, 10, 0);
            var eTag = new EntityTagHeaderValue(hashedOrderEtag);

            // Retrieve the If-None-Match header from the request (if it exists)
            var nonMatchEtags = Request.Headers.IfNoneMatch;

            // If there is an ETag in the If-None-Match header and
            // this ETag matches that of the order just retrieved,
            // then create a Not Modified response message
            if (nonMatchEtags.Count > 0 &&
                String.CompareOrdinal(nonMatchEtags.First().Tag, hashedOrderEtag) == 0)
            {
                response = new EmptyResultWithCaching()
                {
                    StatusCode = HttpStatusCode.NotModified,
                    CacheControlHeader = cacheControlHeader,
                    ETag = eTag
                };
            }
            // Otherwise create a response message that contains the order details
            else
            {
                response = new OkResultWithCaching<Order>(order, this)
                {
                    CacheControlHeader = cacheControlHeader,
                    ETag = eTag
                };
            }

            return response;
        }
        catch
        {
            return InternalServerError();
        }
    }
...
}

Dit voorbeeld bevat een aanvullende aangepaste IHttpActionResult-klasse met de naam EmptyResultWithCaching. Deze klasse fungeert gewoon als een verpakking rond een HttpResponseMessage-object dat geen antwoordtekst bevat:

public class EmptyResultWithCaching : IHttpActionResult
{
    public CacheControlHeaderValue CacheControlHeader { get; set; }
    public EntityTagHeaderValue ETag { get; set; }
    public HttpStatusCode StatusCode { get; set; }
    public Uri Location { get; set; }

    public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response = new HttpResponseMessage(StatusCode);
        response.Headers.CacheControl = this.CacheControlHeader;
        response.Headers.ETag = this.ETag;
        response.Headers.Location = this.Location;
        return response;
    }
}

Tip

In dit voorbeeld wordt de ETag voor de gegevens gegenereerd door de gegevens te hashen die zijn opgehaald uit de onderliggende gegevensbron. Als de ETag op een andere manier wordt berekend, kan het proces verder worden geoptimaliseerd en moeten de gegevens alleen worden opgehaald uit de gegevensbron als ze zijn gewijzigd. Deze aanpak is vooral nuttig als de gegevens te groot zijn, of toegang tot de gegevensbron tot aanzienlijke latentie kan leiden (bijvoorbeeld als de gegevensbron een externe database is).

Gebruik ETags ter ondersteuning van Optimistische gelijktijdigheid

Om het bijwerken via eerder opgeslagen cachegegevens mogelijk te maken, ondersteunt het HTTP-protocol een optimistische gelijktijdigheid-strategie. Als de clienttoepassing, na ophalen en opslaan in cache van een resource, vervolgens een PUT- of DELETE-aanvraag stuurt om de resource te wijzigen of verwijderen, moet deze een If-Match-header bevatten die verwijst naar de ETag. De web-API kan vervolgens deze informatie gebruiken om te bepalen of de resource al is gewijzigd door een andere gebruiker sinds die is opgehaald, en als volgt een juiste reactie terugsturen naar de clienttoepassing:

  • De client bouwt een PUT-aanvraag die de nieuwe details bevat voor de resource en de ETag voor de versie van de resource die momenteel in de cache is opgeslagen, waarnaar wordt verwezen in een If-None-Match HTTP-header. Het volgende voorbeeld toont een PUT-aanvraag die een order bijwerkt:

    PUT https://adventure-works.com/orders/1 HTTP/1.1
    If-Match: "2282343857"
    Content-Type: application/x-www-form-urlencoded
    Content-Length: ...
    productID=3&quantity=5&orderValue=250
    
  • De PUT-bewerking in de web-API bevat de huidige ETag voor de aangevraagde gegevens (volgorde 1 in het bovenstaande voorbeeld) en vergelijkt deze met de waarde in de If-None-Match-header.

  • Als de huidige ETag voor de aangevraagde gegevens overeenkomt met de ETag die is geleverd door de aanvraag, is de bron niet gewijzigd en moet de web-API de update uitvoeren, en een bericht retourneren met HTTP-statuscode 204 (Niet gewijzigd) als deze is geslaagd. Het antwoord kan Cache-Control- en ETag-headers bevatten voor de bijgewerkte versie van de resource. Het antwoord moet altijd de Locatie-header bevatten die verwijst naar de URI van de bijgewerkte resource.

  • Als de huidige ETag voor de aangevraagde gegevens niet overeenkomt met de ETag die is geleverd door de aanvraag, zijn de gegevens gewijzigd door een andere gebruiker sinds ze zijn opgehaald en moet de web-API een HTTP-antwoord met een lege berichttekst en statuscode 412 (Voorwaarde niet voldaan) retourneren.

  • Als de bij te werken resource niet meer bestaat, moet de web-API een HTTP-antwoord retourneren met de statuscode 404 (Niet gevonden).

  • De client gebruikt de statuscode en antwoord-headers voor het onderhouden van de cache. Als de gegevens zijn bijgewerkt (statuscode 204), kan het object in de cache blijven (zolang de Cache-Control-header geen no-store opgeeft), maar de ETag moet worden bijgewerkt. Als de gegevens zijn gewijzigd door een andere gebruiker (statuscode 412) of niet gevonden (statuscode 404), moet het object in de cache worden verwijderd.

Het volgende codevoorbeeld toont een implementatie van de PUT-bewerking voor de Orders-controller:

public class OrdersController : ApiController
{
    [HttpPut]
    [Route("api/orders/{id:int}")]
    public IHttpActionResult UpdateExistingOrder(int id, DTOOrder order)
    {
        try
        {
            var baseUri = Constants.GetUriFromConfig();
            var orderToUpdate = this.ordersRepository.GetOrder(id);
            if (orderToUpdate == null)
            {
                return NotFound();
            }

            var hashedOrder = orderToUpdate.GetHashCode();
            string hashedOrderEtag = $"\"{hashedOrder}\"";

            // Retrieve the If-Match header from the request (if it exists)
            var matchEtags = Request.Headers.IfMatch;

            // If there is an ETag in the If-Match header and
            // this ETag matches that of the order just retrieved,
            // or if there is no ETag, then update the Order
            if (((matchEtags.Count > 0 &&
                String.CompareOrdinal(matchEtags.First().Tag, hashedOrderEtag) == 0)) ||
                matchEtags.Count == 0)
            {
                // Modify the order
                orderToUpdate.OrderValue = order.OrderValue;
                orderToUpdate.ProductID = order.ProductID;
                orderToUpdate.Quantity = order.Quantity;

                // Save the order back to the data store
                // ...

                // Create the No Content response with Cache-Control, ETag, and Location headers
                var cacheControlHeader = new CacheControlHeaderValue();
                cacheControlHeader.Private = true;
                cacheControlHeader.MaxAge = new TimeSpan(0, 10, 0);

                hashedOrder = order.GetHashCode();
                hashedOrderEtag = $"\"{hashedOrder}\"";
                var eTag = new EntityTagHeaderValue(hashedOrderEtag);

                var location = new Uri($"{baseUri}/{Constants.ORDERS}/{id}");
                var response = new EmptyResultWithCaching()
                {
                    StatusCode = HttpStatusCode.NoContent,
                    CacheControlHeader = cacheControlHeader,
                    ETag = eTag,
                    Location = location
                };

                return response;
            }

            // Otherwise return a Precondition Failed response
            return StatusCode(HttpStatusCode.PreconditionFailed);
        }
        catch
        {
            return InternalServerError();
        }
    }
    ...
}

Tip

Gebruik van de If-Match-header is volledig optioneel en als deze wordt weggelaten zal de web-API altijd proberen de opgegeven order bij te werken en daarbij mogelijk blind een update van een andere gebruiker overschrijven. Om problemen te voorkomen als gevolg van verloren updates, moet u altijd een If-Match-header opgeven.

Verwerken van grote aanvragen en antwoorden

Mogelijk zijn er gevallen wanneer een clienttoepassing aanvragen moet doen die gegevens verzenden of ontvangen, die mogelijk meerdere megabytes (of meer) groot zijn. Wachten terwijl deze hoeveelheid gegevens wordt verzonden, kan ervoor zorgen dat de clienttoepassing niet meer reageert. Houd rekening met de volgende punten als u aanvragen moet verwerken met grote hoeveelheden gegevens:

Optimaliseer aanvragen en antwoorden die betrekking hebben op grote objecten

Sommige resources zijn mogelijk grote objecten of bevatten grote velden, zoals grafische afbeeldingen of andere soorten binaire gegevens. Een web-API moet ondersteuning bieden voor streaming, zodat geoptimaliseerd uploaden en downloaden van deze resources mogelijk is.

Het HTTP-protocol biedt het mechanisme voor gesegmenteerde overdrachtscodering om de grote gegevensobjecten terug te streamen naar een client. Wanneer de client een HTTP GET-aanvraag voor een groot object verzendt, kan de web-API het antwoord verzenden in stuksgewijze chunks via een HTTP-verbinding. De lengte van de gegevens in het antwoord kan in eerste instantie niet bekend zijn (het kan worden gegenereerd), daarom moet de server die als host fungeert voor de web-API een antwoordbericht verzenden met elk segment dat de Transfer-Encoding: Chunked-header opgeeft in plaats van een Content-Length-header. De clienttoepassing kan elke chunk op zijn beurt ontvangen om het volledige antwoord op te bouwen. De gegevensoverdracht is voltooid wanneer de server een laatste chunk terugstuurt ter grootte van nul.

Een enkele aanvraag kan mogelijk leiden tot een enorm object dat aanzienlijke resources verbruikt. Als tijdens het streamingproces de web-API vaststelt dat de hoeveelheid gegevens in een aanvraag bepaalde acceptabele grenzen heeft overschreden, kan de bewerking worden afgebroken en een antwoordbericht met statuscode 413 (Aanvraagentiteit te groot) worden geretourneerd.

U kunt de grootte van grote objecten die via het netwerk worden verzonden met behulp van HTTP-compressie minimaliseren. Deze aanpak helpt bij het verminderen van de hoeveelheid netwerkverkeer en de bijbehorende netwerklatentie, maar vraagt wel extra verwerking door de client en de server die als host fungeert voor de web-API. Een clienttoepassing die bijvoorbeeld verwacht om gecomprimeerde gegevens te ontvangen, kan bijvoorbeeld een Accept-Encoding: gzip-aanvraagheader bevatten (andere gegevenscompressie-algoritmen kunnen ook worden opgegeven). Als de server compressie ondersteunt, moet deze reageren met de inhoud in de gzip-indeling in de berichttekst en de Content-Encoding: gzip response-header.

U kunt gecodeerde compressie combineren met streaming. Comprimeer de gegevens eerst voordat u deze gaat streamen en geef de gzip-inhoudcodering en gesegmenteerde overdrachtscodering op in de berichtkoppen. Houd er ook rekening mee dat bepaalde webservers (zoals Internet Information Server) kunnen worden geconfigureerd om HTTP-antwoorden automatisch te comprimeren, ongeacht of de gegevens door de web-API worden gecomprimeerd of niet.

Gedeeltelijke antwoorden implementeren voor clients die geen ondersteuning bieden voor asynchrone bewerkingen

Als alternatief voor asynchrone streaming kan een clienttoepassing expliciet gegevens voor grote objecten in chunks aanvragen. Deze worden gedeeltelijke antwoorden genoemd. De clienttoepassing stuurt een HTTP HEAD-aanvraag om informatie over het object te verkrijgen. Als de web-API gedeeltelijke antwoorden ondersteunt, moet deze reageren op de HEAD-aanvraag met een antwoordbericht dat een Accept-Ranges-header en een content-length-header bevat die de totale grootte van het object aangeeft, maar de hoofdtekst van het bericht moet leeg zijn. De clienttoepassing kan deze informatie gebruiken om een reeks GET-aanvragen samen te stellen die een range van te ontvangen bytes opgeven. De web-API moet een antwoordbericht retourneren met HTTP-status 206 (Gedeeltelijke inhoud), een Content-Length-header die de werkelijke hoeveelheid gegevens opgeeft die zijn opgenomen in de hoofdtekst van het antwoordbericht en een Content-Range-header die aangeeft voor welk gedeelte (zoals bytes 4000 tot 8000) van het object deze gegevens staan.

HTTP HEAD-aanvragen en gedeeltelijke antwoorden worden gedetailleerder beschreven in API-ontwerp.

Vermijd het verzenden van onnodige 100-continue statusberichten in clienttoepassingen

Een clienttoepassing die binnenkort een grote hoeveelheid gegevens naar een server gaat verzenden, kan mogelijk eerst bepalen of de server daadwerkelijk de aanvraag wil accepteren. Vóór het verzenden van de gegevens kan de clienttoepassing een HTTP-aanvraag met een Expect: 100-Continue-header indienen, een Content-Length-header die de grootte van de gegevens aangeeft, maar met een lege berichttekst. Als de server de aanvraag wil verwerken, moet deze reageren met een bericht dat de HTTP-status 100 (Doorgaan) opgeeft. De clienttoepassing kan nu doorgaan en de volledige aanvraag verzenden, waaronder de gegevens in de hoofdtekst van het bericht.

Als u een service host met behulp van IIS, detecteert en verwerkt het stuurprogramma HTTP.sys automatisch Expect: 100-Continue-headers voordat aanvragen worden doorgegeven aan uw webtoepassing. Dit betekent dat het onwaarschijnlijk is dat u deze headers in uw toepassingscode ziet en u ervan kunt uitgaan dat IIS al de berichten heeft gefilterd die het ongeschikt of te groot acht.

Als u clienttoepassingen bouwt met behulp van de .NET Framework, verzenden alle POST- en PUT-berichten eerst berichten met Verwacht: 100-Doorgaan-headers standaard. Het proces wordt net als bij de serverzijde transparant verwerkt door het .NET Framework. Dit proces leidt er echter toe dat elke POST- en PUT-aanvraag twee retouren naar de server veroorzaakt, zelfs voor kleine aanvragen. Als uw toepassing geen aanvragen met grote hoeveelheden gegevens verzendt, kunt u deze functie uitschakelen met behulp van de ServicePointManager-klasse om ServicePoint-objecten te maken in de clienttoepassing. Een ServicePoint-object verwerkt de verbindingen die de client maakt met een server, op basis van het schema en de host-fragmenten van URI's die resources op de server identificeren. U kunt dan de eigenschap Expect100Continue van het ServicePoint-object op onwaar instellen. Alle volgende POST- en PUT-aanvragen van de client via een URI die overeenkomen met het schema en host-fragmenten van het ServicePoint-object zullen zonder Expect: 100-Continue-headers worden verzonden. De volgende code toont hoe u een ServicePoint-object configureert dat alle aanvragen configureert die worden verzonden naar URI's met een schema van http en een host van www.contoso.com.

Uri uri = new Uri("https://www.contoso.com/");
ServicePoint sp = ServicePointManager.FindServicePoint(uri);
sp.Expect100Continue = false;

U kunt ook de statische Expect100Continue eigenschap van de ServicePointManager klasse instellen om de standaardwaarde van deze eigenschap op te geven voor alle vervolgens gemaakte ServicePoint-objecten .

Ondersteuning voor paginering voor aanvragen die mogelijk grote aantallen objecten retourneren

Als een verzameling een groot aantal resources bevat, kan het uitgeven van een GET-aanvraag naar de overeenkomstige URI leiden tot aanzienlijke bewerkingen op de server die als host fungeert voor de web-API. Dit kan invloed hebben op prestaties en een aanzienlijke hoeveelheid netwerkverkeer genereren, met als gevolg toegenomen latentie.

Voor het afhandelen van dergelijke gevallen moet de web-API queryreeksen ondersteunen die het de clienttoepassing mogelijk maken om aanvragen te verfijnen, of gegevens in meer beheersbare discrete blokken (of pagina's) op te halen. De code hieronder toont de GetAllOrders-methode in de Orders-controller. Deze methode haalt de details van orders op. Als deze methode onbeperkt was, kan die mogelijk een grote hoeveelheid gegevens retourneren. De parameters limit en offset zijn bedoeld om de hoeveelheid gegevens te beperken tot een kleinere subset, in dit geval standaard alleen de eerste 10 orders:

public class OrdersController : ApiController
{
    ...
    [Route("api/orders")]
    [HttpGet]
    public IEnumerable<Order> GetAllOrders(int limit=10, int offset=0)
    {
        // Find the number of orders specified by the limit parameter
        // starting with the order specified by the offset parameter
        var orders = ...
        return orders;
    }
    ...
}

Een clienttoepassing kan een aanvraag voor het ophalen van 30 orders met behulp van de URI https://www.adventure-works.com/api/orders?limit=30&offset=50 uitgeven, vanaf offset 50.

Tip

Geef clienttoepassingen niet de mogelijkheid om queryreeksen op te geven die resulteren in een URI die meer dan 2000 tekens lang is. Veel webclients en -servers kunnen niet omgaan met URI's die zo lang zijn.

Reactiesnelheid, schaalbaarheid en beschikbaarheid onderhouden

Dezelfde web-API kan worden gebruikt door veel clienttoepassingen die overal ter wereld worden uitgevoerd. Het is belangrijk om ervoor te zorgen dat de web-API wordt geïmplementeerd om reactiesnelheid onder zware belasting te behouden, om schaalbaar te zijn ter ondersteuning van een zeer uiteenlopende werkbelasting en om beschikbaarheid te garanderen voor clients die bedrijfskritieke bewerkingen uitvoeren. Houd rekening met de volgende punten wanneer u bepaalt hoe u aan deze vereisten gaat voldoen:

Bied asynchrone ondersteuning voor langlopende aanvragen

Een aanvraag die lang nodig kan hebben om te verwerken, moet worden uitgevoerd zonder de client te blokkeren die de aanvraag heeft ingediend. De web-API kan een aantal initiële controles uitvoeren voor het valideren van de aanvraag, een afzonderlijke taak initiëren voor het uitvoeren van het werk en vervolgens een antwoordbericht met HTTP-code 202 (Geaccepteerd) retourneren. De taak kan asynchroon worden uitgevoerd als onderdeel van het verwerken door de web-API of naar een achtergrondtaak worden geladen.

De web-API moet ook een mechanisme bieden om de resultaten van de verwerking naar de clienttoepassing te retourneren. U kunt dit bereiken door in een polling-mechanisme voor clienttoepassingen te voorzien om regelmatig query's uit te voeren of de verwerking is voltooid en om het resultaat te verkrijgen, of de web-API in te schakelen om een melding te verzenden wanneer de bewerking is voltooid.

U kunt een eenvoudig polling-mechanisme implementeren door een polling-URI op te geven die fungeert als een virtuele resource, met behulp van de volgende benadering:

  1. De clienttoepassing verzendt de eerste aanvraag naar de web-API.
  2. De web-API slaat informatie over de aanvraag op in een tabel in Azure Table Storage of Microsoft Azure Cache en genereert een unieke sleutel voor deze vermelding, mogelijk in de vorm van een GUID. U kunt ook een bericht met informatie over de aanvraag en de unieke sleutel verzenden via Azure Service Bus.
  3. De web-API start de verwerking als een afzonderlijke taak of met een bibliotheek zoals Hangfire. De web-API registreert de status van de taak in de tabel als In uitvoering.
    • Als u Azure Service Bus gebruikt, wordt de berichtverwerking afzonderlijk van de API uitgevoerd, mogelijk met behulp van Azure Functions of AKS.
  4. De web-API retourneert een antwoordbericht met HTTP-statuscode 202 (Geaccepteerd) en een URI met de unieke sleutel die is gegenereerd, bijvoorbeeld /polling/{guid}.
  5. Wanneer de taak is voltooid, slaat de web-API de resultaten op in de tabel en wordt de status van de taak ingesteld op Voltooid. Houd er rekening mee dat als de taak is mislukt, de web-API ook informatie over de fout kan opslaan en de status op Mislukt kan zetten.
  6. Terwijl de taak wordt uitgevoerd, kan de client zijn eigen processen blijven uitvoeren. Het kan periodiek een aanvraag verzenden naar de URI die hij eerder heeft ontvangen.
  7. De web-API op de URI voert een query uit naar de status van de bijbehorende taak in de tabel en retourneert een antwoordbericht met HTTP-statuscode 200 (OK) met deze status (Wordt uitgevoerd, Voltooid of Mislukt). Als de taak is voltooid of mislukt, kan het antwoordbericht ook de resultaten van de verwerking of alle beschikbare informatie over de oorzaak van het probleem bevatten.
    • Als het langlopende proces meer tussenliggende statussen heeft, is het beter om een bibliotheek te gebruiken die het saga-patroon ondersteunt, zoals NServiceBus of MassTransit.

Opties voor het implementeren van meldingen zijn onder andere:

  • Een Notification Hub gebruiken om asynchrone antwoorden naar clienttoepassingen te pushen. Zie Meldingen verzenden naar specifieke gebruikers met behulp van Azure Notification Hubs voor meer informatie.
  • Het Comet-model voor het bewaren van een permanente netwerkverbinding tussen de client en de server die als host fungeert voor de web-API gebruiken en deze verbinding gebruiken om berichten van de server terug te pushen naar de client. Het MSDN Magazine-artikel Building a Simple Comet Application in the Microsoft .NET Framework (Een eenvoudige Comet-toepassing in Microsoft .NET Framework bouwen) beschrijft een voorbeeldoplossing.
  • SignalR gebruiken om gegevens in realtime van de webserver naar de client te pushen via een permanente netwerkverbinding. SignalR is voor ASP.NET-webtoepassingen beschikbaar als een NuGet-pakket. U kunt meer informatie vinden op de ASP.NET SignalR-website.

Zorg ervoor dat elke aanvraag staatloos is

Elke aanvraag moet als atomisch worden beschouwd. Er mogen geen afhankelijkheden zijn tussen één aanvraag die is ingediend door een clienttoepassing en alle volgende aanvragen die zijn verzonden door dezelfde client. Deze aanpak is nuttig voor de schaalbaarheid. Instanties van de webservice kunnen worden geïmplementeerd op een aantal servers. Aanvragen van clients worden bij elk van deze instanties omgeleid en de resultaten moeten altijd hetzelfde zijn. Dit verbetert tevens de beschikbaarheid om dezelfde reden. Als een webserver mislukt, kunnen aanvragen worden doorgestuurd naar een andere instantie (via Azure Traffic Manager) terwijl de server opnieuw wordt opgestart zonder nadelige effecten op clienttoepassingen te hebben.

Volg clients en implementeer netwerkbeperking om de kans op DOS-aanvallen te reduceren

Als een specifieke client een groot aantal aanvragen maakt binnen een bepaalde periode, kan deze mogelijk de service in beslag nemen en van invloed zijn op de prestaties van andere clients. Om dit probleem te beperken, kan een web-API aanroepen vanuit clienttoepassingen bewaken door het IP-adres van alle inkomende aanvragen bij te houden of door logboekregistratie van elke geverifieerde toegang. U kunt deze informatie gebruiken om de toegang tot bedrijfsresources te beperken. Als een client een vastgestelde limiet overschrijdt, kan de web-API een antwoordbericht retourneren met de status 503 (Service niet beschikbaar) en een Retry-After-header meegeven die aangeeft wanneer de client de volgende aanvraag kan verzenden zonder te worden geweigerd. Deze strategie kan helpen de kans te verminderen dat een Denial of Service-aanval (DOS) uit een set van clients het systeem ophoudt.

Beheer permanente HTTP-verbindingen zorgvuldig

Het HTTP-protocol biedt ondersteuning voor permanente HTTP-verbindingen, waar deze beschikbaar zijn. De HTTP 1.0-specificatie heeft de header Connection:Keep-Alive toegevoegd waarmee een clienttoepassing aan de server kan aangeven dat dezelfde verbinding kan worden gebruikt om volgende aanvragen te verzenden in plaats van nieuwe aanvragen te openen. De verbinding wordt automatisch gesloten als de client de verbinding niet binnen een periode, die is gedefinieerd door de host, opnieuw gebruikt. Dit gedrag is de standaardwaarde in HTTP 1.1 zoals het wordt gebruikt door Azure-services, dus u hoeft geen Keep-Alive-headers in berichten op te nemen.

Het openhouden van een verbinding kan het reactievermogen verbeteren door latentie en netwerkcongestie te verminderen, maar het kan schadelijk zijn voor schaalbaarheid door overbodige verbindingen langer dan vereist aan te houden, en hiermee voor andere gelijktijdige clients de mogelijkheid beperken om verbinding te maken. Het kan ook de batterijduur aantasten als de clienttoepassing wordt uitgevoerd op een mobiel apparaat. Als de toepassing alleen incidenteel aanvragen bij de server indient, kan het onderhouden van een open verbinding ervoor zorgen dat de batterij sneller leeg raakt. Om ervoor te zorgen dat een verbinding niet permanent is gemaakt met HTTP 1.1, kan de client een Connection:Close-header opnemen met berichten om het standaardgedrag te negeren. Op dezelfde manier kan een server, als deze een zeer groot aantal clients verwerkt, een Connection:Close-header in antwoordberichten opnemen die de verbinding moeten sluiten en serverresources besparen.

Notitie

Permanente HTTP-verbindingen zijn een zuiver optionele functie om de netwerkbelasting te beperken die is gekoppeld aan het herhaaldelijk tot stand brengen van een communicatiekanaal. Noch de web-API, noch de clienttoepassing moet ervan afhankelijk zijn of een permanente HTTP-verbinding beschikbaar is. Gebruik geen permanente HTTP-verbindingen om comet-meldingssystemen te implementeren; Gebruik in plaats daarvan sockets (of websockets indien beschikbaar) op de TCP-laag. Houd er ten slotte rekening mee dat Keep-Alive-headers van beperkte waarde zijn als een clienttoepassing via een proxy met een server communiceert. Alleen de verbinding tussen de proxy en de client is permanent.

Publiceren en beheren van een web-API

Om een web-API voor clienttoepassingen beschikbaar te stellen, moet de web-API worden geïmplementeerd op een hostomgeving. Deze omgeving is meestal een webserver, hoewel het mogelijk een ander type hostproces is. Bij het publiceren van een web-API, moet u de volgende punten overwegen:

  • Alle aanvragen moeten worden geverifieerd en geautoriseerd en het juiste niveau van toegangsbeheer moet worden afgedwongen.
  • Een commerciële web-API is mogelijk onderhevig aan verschillende kwaliteitsgaranties voor de reactietijden. Het is belangrijk om ervoor te zorgen dat de hostomgeving schaalbaar is als de belasting in de loop van de tijd aanzienlijk kan variëren.
  • Het is mogelijk nodig om aanvragen te meten voor monetaire doeleinden.
  • Het kan nodig zijn om de stroom van het verkeer naar de web-API te reguleren en netwerkbeperking te implementeren voor specifieke clients die hun quota hebben verbruikt.
  • Juridische vereisten verplichten mogelijk logboekregistratie en controle van alle aanvragen en antwoorden.
  • Om voor beschikbaarheid te zorgen, is het mogelijk nodig om de status van de server te controleren die als host fungeert voor de web-API en deze opnieuw op te starten indien nodig.

Het is handig om deze problemen los te koppelen van de technische problemen met betrekking tot de implementatie van de web-API. Daarom kunt u overwegen om een gevel te maken, die wordt uitgevoerd als een afzonderlijk proces en dat aanvragen doorstuurt naar de web-API. De gevel kan de beheerbewerkingen leveren en gevalideerde aanvragen doorsturen naar de web-API. Het gebruik van een gevel kan ook veel functionele voordelen met zich meebrengen, waaronder:

  • Fungeren als een integratiepunt voor meerdere web-API's.
  • Berichten transformeren en vertalen van de communicatieprotocollen voor clients die zijn gebouwd met behulp van verschillende technologieën.
  • De belasting van de server die als host fungeert voor de web-API verminderen door aanvragen en antwoorden in de cache op te slaan.

Een web-API testen

Een web-API moet even grondig worden getest als elk ander deel van de software. U moet overwegen om eenheidstests te maken om de functionaliteit te valideren.

De aard van een web-API brengt eigen aanvullende vereisten met zich mee om te controleren of deze correct werkt. U moet speciale aandacht besteden aan de volgende aspecten:

  • Test alle routes om te controleren of ze de juiste bewerkingen aanroepen. Houd met name rekening met de HTTP-statuscode 405 (Methode niet toegestaan), want als deze onverwacht wordt geretourneerd kan dit duiden op een discrepantie tussen een route en de HTTP-methoden (GET, POST, PUT, DELETE) die kunnen worden verzonden naar die route.

    HTTP-aanvragen verzenden naar routes die deze niet ondersteunen, zoals het verzenden van een POST-aanvraag naar een specifieke resource (POST-aanvragen moeten alleen worden verzonden naar resourceverzamelingen). In deze gevallen moet de enige geldige reactie statuscode 405 (Niet toegestaan) zijn.

  • Controleer of alle routes correct zijn beveiligd en de juiste verificatie en autorisatiecontroles worden uitgevoerd.

    Notitie

    Sommige aspecten van beveiliging, zoals verificatie van de gebruiker, zijn waarschijnlijk de verantwoordelijkheid van de hostomgeving in plaats van de web-API, maar het is nog steeds nodig om beveiligingstests op te nemen als onderdeel van het implementatieproces.

  • Test de afhandeling van uitzonderingen die door elke bewerking wordt uitgevoerd en controleer of een geschikt en zinvol HTTP-antwoord wordt doorgegeven aan de clienttoepassing.

  • Controleer of de aanvraag- en antwoordberichten goed geformuleerd zijn. Bijvoorbeeld, als een HTTP POST-aanvraag de gegevens voor een nieuwe resource in de x-www-form-urlencoded-indeling bevat, bevestigt u dat de bijbehorende bewerking de gegevens correct parseert, de resources maakt en een antwoord retourneert met de details van de nieuwe resource, met inbegrip van de juiste Location-header.

  • Controleer alle koppelingen en URI's in antwoordberichten. Een HTTP POST-bericht moet bijvoorbeeld de URI van de zojuist gemaakte resource retourneren. Alle HATEOAS-koppelingen moeten geldig zijn.

  • Zorg ervoor dat elke bewerking de juiste statuscodes voor verschillende invoercombinaties retourneert. Bijvoorbeeld:

    • Als een query geslaagd is, moet deze statuscode 200 retourneren (OK)
    • Als een bron niet gevonden is, moet de bewerking HTTP-statuscode 404 (Niet gevonden) retourneren.
    • Als de client een aanvraag verzendt die een resource verwijdert, moet de statuscode 204 zijn (Geen inhoud).
    • Als de client een aanvraag verzendt waarmee een nieuwe resource wordt gemaakt, moet de statuscode 201 (Gemaakt) zijn.

Kijk uit voor onverwachte antwoord-statuscodes in het bereik 5xx. Deze berichten worden gewoonlijk gerapporteerd door de hostserver om aan te geven dat deze niet aan een geldige aanvraag kon voldoen.

  • Test de verschillende combinaties van aanvragen en headers die een clienttoepassing kan opgeven en zorg ervoor dat de web-API de verwachte gegevens retourneert in antwoordberichten.

  • Queryreeksen testen. Als een bewerking optionele parameters kan aannemen (zoals paginering aanvragen), test u de verschillende combinaties en de volgorde van de parameters.

  • Controleer of de asynchrone bewerkingen zijn voltooid. Als de web-API streaming voor aanvragen ondersteunt, die resulteren in grote binaire objecten (zoals een video of audio), zorgt u ervoor dat clientaanvragen niet worden geblokkeerd terwijl de gegevens worden gestreamd. Als de web-API polling implementeert voor langdurige bewerkingen voor het wijzigen van gegevens, controleert u of de bewerkingen hun status correct rapporteren terwijl ze doorgaan.

U moet ook prestatietests maken en uitvoeren om te controleren of de web-API naar behoren functioneert onder stress. U kunt een webprestatie en load-testproject opbouwen met behulp van Visual Studio Ultimate.

Azure API Management gebruiken

In Azure kunt u overwegen om Azure API Management te gebruiken om een web-API te publiceren en te beheren. Met behulp van deze faciliteit kunt u een service genereren die fungeert als een gevel voor een of meer web-API's. De service is zelf een schaalbare webservice die u kunt maken en configureren met behulp van de Azure Portal. U kunt deze service als volgt gebruiken om een web-API te publiceren en beheren:

  1. Implementeer de web-API op een website, Azure-cloudservice of virtuele machine van Azure.

  2. Verbind de API Management-service met de web-API. Aanvragen die worden verzonden naar de URL van de management-API zijn toegewezen aan de URI's in de web-API. Dezelfde API Management-service kan aanvragen versturen aan meer dan één web-API. Hiermee kunt u meerdere web-API's in een enkele management-service combineren. Op deze manier kan naar dezelfde web-API door meer dan één API management-service worden verwezen als u de functionaliteit wilt beperken of partitioneren, die beschikbaar is voor verschillende toepassingen.

    Notitie

    De URI's in de HATEOAS-koppelingen die worden gegenereerd als onderdeel van het antwoord op HTTP GET-aanvragen, moeten verwijzen naar de URL van de API Management-service en niet naar de webserver die als host fungeert voor de web-API.

  3. Geef voor elke web-API de HTTP-bewerkingen die de web-API beschikbaar maakt, samen met eventuele optionele parameters die door een bewerking als invoer kunnen worden uitgevoerd. U kunt ook configureren of de API Management-service het antwoord van de web-API in de cache moet opslaan om herhaalde verzoeken voor dezelfde gegevens te optimaliseren. Noteer de details van de HTTP-antwoorden die elke bewerking kan genereren. Deze informatie wordt gebruikt voor het genereren van documentatie voor ontwikkelaars, dus is het belangrijk dat deze juist en volledig is.

    U kunt bewerkingen handmatig definiëren met behulp van de wizards van de Azure Portal, of u kunt ze importeren uit een bestand met de definities in WADL- of Swagger-indeling.

  4. Configureer de beveiligingsinstellingen voor de communicatie tussen de API Management-service en de webserver die als host fungeert voor de web-API. De API Management-service ondersteunt momenteel basisverificatie en wederzijdse verificatie met behulp van certificaten en gebruikersautorisatie OAuth 2.0.

  5. Maak een product. Een product is de eenheid van publicatie. U voegt de web-API's die u eerder hebt verbonden met de beheerservice, toe aan het product. Wanneer het product wordt gepubliceerd, worden de web-API's beschikbaar voor ontwikkelaars.

    Notitie

    Voor het publiceren van een product kunt u ook gebruikersgroepen definiëren die toegang kunnen krijgen tot het product en gebruikers toevoegen aan deze groepen. Dit geeft u controle over de ontwikkelaars en toepassingen die de web-API kunnen gebruiken. Wanneer een web-API moet worden goedgekeurd, moet een ontwikkelaar een aanvraag verzenden naar de productbeheerder ervan, voordat hij er toegang toe krijgt. De beheerder kan toegang aan de ontwikkelaar verlenen of weigeren. Ook bestaande ontwikkelaars kunnen worden geblokkeerd als de omstandigheden wijzigen.

  6. Configureer beleidsregels voor elke web-API. Beleidsregels beheren aspecten zoals het toestaan van aanroepen tussen domeinen, hoe clients moeten worden geverifieerd, of gegevensindelingen transparant tussen XML en JSON moeten worden geconverteerd, of aanroepen vanuit een bepaald IP-adresbereik worden beperkt, quota voor gebruik en of de aanroepfrequentie moet worden beperkt. Beleid kan globaal worden toegepast op het gehele product, voor een enkele web-API in een product of voor afzonderlijke bewerkingen in een web-API.

Zie de API Management documentatie voor meer informatie.

Tip

Azure biedt Azure Traffic Manager waarmee u failover en taakverdeling kunt implementeren en de latentie kunt verminderen voor meerdere instanties van een website die wordt gehost op verschillende geografische locaties. U kunt Azure Traffic Manager gebruiken in combinatie met de API Management-Service. De API Management-Service kan aanvragen versturen naar instanties van een website via Azure Traffic Manager. Zie Traffic Manager-routeringsmethoden voor meer informatie.

Als u in deze structuur aangepaste DNS-namen gebruikt voor uw websites, moet u de juiste CNAME-record voor elke website configureren om te verwijzen naar de DNS-naam van de Azure Traffic Manager-website.

Ondersteuning voor ontwikkelaars aan clientzijde

Ontwikkelaars die clienttoepassingen construeren vereisen doorgaans informatie over de toegang tot de web-API en documentatie over de parameters, gegevenstypen, retourtypen en retourcodes die de verschillende aanvragen en antwoorden tussen de webservice en de clienttoepassing beschrijven.

Documenteer de REST-bewerkingen voor een web-API

De Azure API Management-Service omvat een portal voor ontwikkelaars dat de REST-bewerkingen beschrijft die beschikbaar worden gemaakt door een web-API. Wanneer een product is gepubliceerd, wordt dit weergegeven op deze portal. Ontwikkelaars kunnen deze portal gebruiken om zich aan te melden voor toegang. De beheerder kan vervolgens de aanvraag goedkeuren of weigeren. Als de ontwikkelaar is goedgekeurd, krijgt hij een abonnementssleutel die wordt gebruikt voor verificatie van aanroepen vanuit de clienttoepassingen die hij ontwikkelt. Deze sleutel moet worden opgegeven met elke web-API-aanroep, anders wordt dit geweigerd.

De portal bevat ook:

  • Documentatie voor het product, die de bewerkingen weergeeft die beschikbaar worden gemaakt, de vereiste parameters en de verschillende antwoorden die kunnen worden geretourneerd. Houd er rekening mee dat deze informatie wordt gegenereerd vanuit de informatie in stap 3 in de lijst, in de sectie Publicatie van een web-API met behulp van de Microsoft Azure API Management Service.
  • Codefragmenten die laten zien hoe u bewerkingen moet aanroepen vanuit verschillende talen, waaronder JavaScript, C#, Java, Ruby, Python en PHP.
  • Een ontwikkelaarsconsole waarmee een ontwikkelaar een HTTP-aanvraag kan verzenden om elke bewerking in het product te testen en de resultaten te bekijken.
  • Een pagina waar de ontwikkelaar gevonden problemen of kwesties kan rapporteren.

Met de Azure Portal kunt u de ontwikkelaarsportal aanpassen om de stijl en indeling aan te passen aan de huisstijl van uw organisatie.

Een client-SDK implementeren

Een clienttoepassing bouwen die REST-aanvragen aanroept voor toegang tot een web-API, vereist het schrijven van een aanzienlijke hoeveelheid code voor het maken en op de juiste wijze formatteren van elke aanvraag, de aanvraag verzenden naar de server die als host fungeert voor de webservice, het parseren van het antwoord om te berekenen of de aanvraag is geslaagd of mislukt en het ophalen van gegevens die zijn geretourneerd. Als u de clienttoepassing voor deze problemen wilt afschermen, kunt u een SDK voorzien die de REST-interface verpakt en deze details op laag niveau verpakt in een aantal methoden die meer functionaliteit bieden. Een clienttoepassing maakt gebruik van deze methoden, die transparant aanroepen converteert naar REST-aanvragen en vervolgens de antwoorden weer converteert naar de retourwaarden van de methode. Dit is een algemene techniek die door veel services wordt geïmplementeerd, waaronder de Azure SDK.

Het maken van een SDK aan clientzijde is een grote onderneming, omdat deze consistent geïmplementeerd en zorgvuldig getest moet worden. Veel van dit proces kan echter mechanisch worden gemaakt en veel leveranciers leveren hulpprogramma's die veel van deze taken kunnen automatiseren.

Een web-API bewaken

Afhankelijk van hoe u hebt gepubliceerd en uw web-API hebt geïmplementeerd, kunt u de web-API rechtstreeks bewaken, of gebruiks- en statusgegevens verzamelen door het verkeer te analyseren dat wordt doorgegeven via de API Management-service.

Een web-API rechtstreeks bewaken

Als u uw web-API hebt geïmplementeerd met behulp van het ASP.NET Web API-sjabloon (hetzij als een Web API-project, hetzij als een Webrol in een Azure-cloudservice) en Visual Studio 2013, kunt u de beschikbaarheid, prestaties en gebruiksgegevens verzamelen met behulp van ASP.NET Application Insights. Application Insights is een pakket dat transparant informatie over aanvragen en antwoorden bijhoudt en registreert als de web-API wordt geïmplementeerd in de cloud. Zodra het pakket is geïnstalleerd en geconfigureerd, hoeft u geen code in uw web-API te wijzigen om het te gebruiken. Wanneer u de web-API op een Azure-website implementeert, wordt al het verkeer onderzocht en worden de volgende statistische gegevens verzameld:

  • Serverreactietijd.
  • Het aantal serveraanvragen en de details van elke aanvraag.
  • De top traagste aanvragen in termen van gemiddelde reactietijd.
  • De details van alle mislukte aanvragen.
  • Het aantal sessies dat door verschillende browsers en gebruikersagenten is gestart.
  • De meest bekeken pagina's (eerder handig voor webtoepassingen dan voor web-API's).
  • De verschillende gebruikersrollen die toegang tot de web-API hebben.

U kunt deze gegevens in realtime bekijken in de Azure Portal. U kunt ook webtests maken die de status van de web-API bewaken. Een webtest verzendt een periodieke aanvraag naar een opgegeven URI in de web-API en legt het antwoord vast. U kunt de definitie van een geslaagd antwoord (zoals HTTP-statuscode 200) opgeven en als de aanvraag niet in dit antwoord resulteert, kunt u instellen dat een waarschuwing wordt verzonden naar een beheerder. Indien nodig, kan de beheerder de server waarop de web-API wordt gehost opnieuw opstarten, als deze is mislukt.

Zie voor meer informatie Application Insights instellen voor uw ASP.NET-website.

Bewaking van een web-API via de API Management-Service

Als u uw web-API hebt gepubliceerd met behulp van de API Management-service, bevat de API Management-pagina op de Azure Portal een dashboard waarmee u de algehele prestaties van de service kunt bekijken. Met de pagina Analytics kunt u inzoomen op de details over hoe het product wordt gebruikt. Deze pagina bevat de volgende tabbladen:

  • Gebruik. Dit tabblad bevat informatie over het aantal uitgevoerde API-aanroepen en de bandbreedte die wordt gebruikt voor het verwerken van deze aanroepen gedurende een bepaalde periode. U kunt gebruiksgegevens filteren op product, API en bewerking.
  • Gezondheid. Op dit tabblad kunt u het resultaat zien van de API-aanvragen (de geretourneerde HTTP-statuscodes), de effectiviteit van het cachebeleid, de reactietijd van de API en de reactietijd van de service. U kunt ook hier gebruiksgegevens filteren op product, API en bewerking.
  • Activiteit. Dit tabblad bevat een samenvatting in tekst van het aantal geslaagde aanroepen, mislukte aanroepen geblokkeerde aanroepen, gemiddelde reactietijd en reactietijden voor elk product, elke web-API en bewerking. Deze pagina bevat ook het aantal aanroepen van elke ontwikkelaar.
  • In één oogopslag. Dit tabblad geeft een samenvatting van de prestatiegegevens, met inbegrip van de ontwikkelaars die verantwoordelijk zijn voor de meeste API-aanroepen en de producten, web-API's en bewerkingen die deze aanroepen ontvangen.

U kunt deze informatie gebruiken om te bepalen of een bepaalde web-API of bewerking een knelpunt veroorzaakt, en indien nodig de hostomgeving schalen en meer servers toevoegen. U kunt ook controleren of één of meer toepassingen een onevenredige hoeveelheid resources gebruiken en het juiste beleid toepassen om quota in te stellen en aanroepfrequenties te beperken.

Notitie

U kunt de details voor een gepubliceerd product wijzigen. De wijzigingen worden onmiddellijk toegepast. U kunt bijvoorbeeld een bewerking van een web-API toevoegen of verwijderen, zonder dat het nodig is om het product dat de web-API bevat opnieuw te publiceren.

Volgende stappen