Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Owin/Katana modifier le header

Poster un commentaire

Dans l’article précédent nous avons détaillé un problème lié à l’envoie d’un message dans le body Http qui dans certains cas pouvait n’être que partiel. Vous n’avez pourtant aucun souvenir d’avoir eu à vous préoccuper de cela en ASP.NET ou en MVC et tout marchait très bien.

La raison est que je n’ai pas tout dit et en particulier que les exemples donnés étaient lancé dans le cadre d’un auto hébergement, c’est à dire en dehors de IIS. Ca change quoi?

Owin est pour l’instant livré avec deux serveurs Http:

  • HttpListener
    Hébergement dans IIS
  • SystemWeb
    auto hébergement

Dans le premier cas le flux Http est bufferisé par défaut, dans le deuxième cas il est émit en mode streaming.

Bufferisé signifie que le flux est transmit en entier, une fois qu’il est terminé. Tant qu’il n’a pas été émit il est alors possible de le modifier.

Streaming signifie qu’on émet le flux dès qu’un message a été écrit avec Write ou WriteAsync.

Dans ce cas le header est émit dès le premier appel à cette fonction et ne pourra donc plus être modifié par la suite.

Regardons maintenant cet exemple de code:

app.Use(async (context, next) =>
{
    await next.Invoke();
    // ici un test quelconque
    context.Response.Headers.Add("TEST", new[] { "OK" });
});

app.Use(async (context, next) =>
{
    string message = "<h1>Hello Fredo</h1>";
    context.Response.ContentType = "text/html;charset=utf-8";
    context.Response.ContentLength = message.Length;

    await context.Response.WriteAsync(message);
    await next.Invoke();
});

 

Le premier middleware ne s’active que lors de la redescente du pipeline en raison de la position du await next. Un scénario typique serait de surveiller le code de retour de la réponse et par exemple ajouter des informations de débogage dans le header.

Justement quel est t’il?

27-04-2014 22-53-09On constate que le header TEST n’apparaît pas.

 

Le problème est dans le deuxième middleware. Celui-ci initialise quelques éléments du headers puis écrit dans le flux Http. On est en mode Stream, par conséquent au moment où commence l’écriture du body du message, le header est immédiatement émit vers le client Http et n’est donc plus disponible une fois que l’on retourne vers le premier middleware.

Ce comportement est le comportement par défaut du pipeline auto-hébergé. Mais dans le cas présent il est problématique. On peut gérer le problème de deux façons.

S’abonner à SendingHeaders

Le middleware pourrait être réécrit ainsi:


app.Use(async (context, next) =>
{
   context.Response.OnSendingHeaders(state =>
   {
      var contexte = (OwinContext)state;
      // ici un test quelconque
      contexte.Response.Headers.Add("TEST", new[] { "OK" });
   }
   , context);

   await next.Invoke();
});

Note: Il faut bien faire attention à ne pas placer le await avant bien sûr!

Response expose la méthode OnSendingHeaders. Cette méthode permet d’abonner une méthode anonyme à l’événement du même nom. Cette méthode est un gestionnaire d’événement donc.

Regardez sa signature:

Action<Object> callback

Action signifie que la méthode est void. Le paramètre est une l’instance du contexte que l’on devra utiliser dans le gestionnaire.

Le deuxième paramètre de OnSendindHeaders est simplement l’instance de la Response en cours.

 

L’événement est levé juste avant que le header soit émit dans le flux. Il offre donc une possibilité à tout middleware d’écrire dans le header. C’est ce que nous avons fait:

29-04-2014 22-30-30

Ca marche donc.

Basculer le pipeline en mode buffer

Nous allons bufferiser le flux de réponse. C’est ce que fait par défaut le lecteur Http IIS.

Voici le code:


app.Use(async (context, next) =>
{
     // montée du pipeline
     var sauvegarde = context.Response.Body;
     MemoryStream ms = new MemoryStream();
     context.Response.Body = ms;

     await next.Invoke();

     // redescente du pipeline
     context.Response.ContentLength = ms.Length;
     ms.Seek(0, SeekOrigin.Begin);
     await ms.CopyToAsync(sauvegarde);
});

app.Use(async (context, next) =>
{
     string message = "<h1>Hello Fredo</h1>";
     context.Response.ContentType = "text/html;charset=utf-8";
     context.Response.ContentLength = message.Length;

     await context.Response.WriteAsync(message);
     await next.Invoke();
});

app.Run(async context =>
{
     context.Response.Headers.Add("TEST", new[] { "OK" });
     string message = "<h1>Fin du pipeline!</h1>";
     context.Response.ContentType = "text/html;charset=utf-8";
     await context.Response.WriteAsync(message);
});

 

Notre pipeline contient 3 middlewares.

Le premier est purement technique, il est destiné à mettre en place la plomberie et la gérer. C’est grâce à lui que tout fonctionne.

On remplace le flux par défaut par un MemoryStream ce qui permet de bufferiser toutes les écritures dans la réponse Http bien que l’on soit en auto-hébergement.

A la fin de l’opération, donc en redescente du pipeline, on remet les choses en place pour que le pipeline puisse fonctionner de façon normale.

La technique ne pose pas de difficulté si ce n’est de ne pas oublier d’ajuster la taille de la réponse sinon ça ne marche pas.

Intéressons nous aux deux autres pipeline.

Tous les deux écrivent dans la réponse Http et cette écriture est effectivement retrouvée dans la page Web:

 

29-04-2014 23-16-59

Vous vous souvenez de l’article précédent, sans le buffer, le flux aurait été émis immédiatement et la deuxième ligne ne s’afficherai pas.

La mise en buffer explique aussi pourquoi on peut écrire  de façon normale dans le header, bien qu’une écriture de body a été faite.

 

Conclusions:

On a mis à jour deux techniques importante pour contrôler l’écriture dans la réponse et dans le header d’un pipeline Katana. Ces méthodes sont importantes.

 

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s