web-dev-qa-db-fra.com

Quelle est la différence entre ResponseWriter.Write et io.WriteString?

J'ai vu trois façons d'écrire du contenu dans une réponse HTTP:

func Handler(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "blabla.\n")
}

Et:

func Handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("blabla\n"))
}

Il y a aussi:

fmt.Fprintf(w, "blabla")

Quelle est la différence entre eux? Lequel est préférable d'utiliser?

27
laike9m

io.Writer

Un flux de sortie représente une cible sur laquelle vous pouvez écrire des séquences d'octets. Dans Go, cela est capturé par l'interface générale io.Writer :

type Writer interface {
    Write(p []byte) (n int, err error)
}

Tout ce qui a cette seule méthode Write() peut être utilisé comme sortie, par exemple un fichier sur votre disque ( os.File ), une connexion réseau ( net.Conn ) ou un tampon en mémoire ( bytes.Buffer ).

Le http.ResponseWriter utilisé pour configurer la réponse HTTP et envoyer les données au client est également un tel io.Writer, Les données que vous souhaitez envoyer (le corps de la réponse ) est assemblé en appelant (pas nécessairement une seule fois) ResponseWriter.Write() (qui consiste à implémenter le général io.Writer). C'est la seule garantie que vous avez sur l'implémentation de l'interface http.ResponseWriter (Concernant l'envoi du corps).

WriteString()

Passons maintenant à WriteString(). Souvent, nous voulons écrire des données textuelles dans un io.Writer. Oui, nous pouvons le faire simplement en convertissant le string en un []byte, Par exemple.

w.Write([]byte("Hello"))

qui fonctionne comme prévu. Cependant, c'est une opération très fréquente et il existe donc une méthode "généralement" acceptée pour cela capturée par l'interface io.StringWriter (disponible depuis Go 1.12 , avant qu'il n'a pas été exporté):

type StringWriter interface {
    WriteString(s string) (n int, err error)
}

Cette méthode donne la possibilité d'écrire une valeur string au lieu d'un []byte. Donc, si quelque chose (qui implémente également io.Writer) Implémente cette méthode, vous pouvez simplement passer des valeurs string sans conversion []byte. Cela semble être une simplification mineure dans le code, mais c'est plus que ça. La conversion d'un string en []byte Doit faire une copie du string contenu (parce que les valeurs string sont immuables dans Go, lisez-en plus ici: golang: [] octet (chaîne) vs [] octet (* chaîne) ), donc là est une surcharge qui devient perceptible si le string est "plus grand" et/ou si vous devez le faire plusieurs fois.

Selon la nature et les détails d'implémentation d'un io.Writer, Il peut être possible d'écrire le contenu d'un string sans le convertir en []byte Et ainsi éviter les frais généraux mentionnés ci-dessus.

Par exemple, si un io.Writer Est quelque chose qui écrit dans un tampon en mémoire (bytes.Buffer En est un exemple), il peut utiliser la fonction intégrée copy() fonction:

La fonction de copie intégrée copie les éléments d'une tranche source dans une tranche de destination. (Dans un cas spécial, il copiera également les octets d'une chaîne dans une tranche d'octets.)

La fonction copy() peut être utilisée pour copier le contenu (octets) d'un string dans un []byte Sans convertir le string en []byte , par exemple:

buf := make([]byte, 100)
copy(buf, "Hello")

Il existe maintenant une fonction "utilitaire" io.WriteString() qui écrit un string dans un io.Writer. Mais il le fait en vérifiant d'abord si le (type dynamique du) passé io.Writer A une méthode WriteString(), et si c'est le cas, celle-ci sera utilisée (dont l'implémentation est probablement plus efficace). Si la io.Writer Passée n'a pas une telle méthode, alors la méthode générale convertir-en-octet-tranche-et-écrire-ça sera utilisée comme "solution de rechange".

Vous pourriez penser que cette WriteString() ne prévaudra qu'en cas de tampons en mémoire, mais ce n'est pas vrai. Les réponses aux demandes Web sont également souvent mises en mémoire tampon (à l'aide d'un tampon en mémoire), ce qui peut également améliorer les performances dans le cas de http.ResponseWriter. Et si vous regardez l'implémentation de http.ResponseWriter: C'est le type non exporté http.response ( server.go actuellement la ligne # 308) qui implémente la fonction WriteString() (actuellement ligne # 1212) donc cela implique une amélioration.

Dans l'ensemble, chaque fois que vous écrivez des valeurs string, il est recommandé d'utiliser io.WriteString() car cela peut être plus efficace (plus rapide).

fmt.Fprintf()

Vous devriez considérer cela comme un moyen pratique et facile d'ajouter plus de mise en forme aux données que vous souhaitez écrire, en échange d'être un peu moins performants.

Utilisez donc fmt.Fprintf() si vous voulez que le format string soit créé de manière simple, par exemple:

name := "Bob"
age := 23
fmt.Fprintf(w, "Hi, my name is %s and I'm %d years old.", name, age)

Ce qui entraînera l'écriture du string suivant:

Hi, my name is Bob and I'm 23 years old.

Une chose que vous ne devez pas oublier: fmt.Fprintf() attend une chaîne format, elle sera donc prétraitée et non écrite telle quelle dans la sortie. Comme exemple rapide:

fmt.Fprintf(w, "100 %%")

Vous vous attendez à ce que "100 %%" Soit écrit dans la sortie (avec 2 % Caractères), mais un seul sera envoyé car dans la chaîne de format % Est un caractère spécial et %% n'entraînera qu'un % dans la sortie.

Si vous voulez simplement écrire un string en utilisant le package fmt, utilisez fmt.Fprint() qui ne nécessite pas de format string :

fmt.Fprint(w, "Hello")

Un autre avantage de l'utilisation du package fmt est que vous pouvez également écrire des valeurs d'autres types, pas seulement string, par exemple.

fmt.Fprint(w, 23, time.Now())

(Bien sûr, les règles de conversion de n'importe quelle valeur en string - et éventuellement en séries d'octets - sont bien définies, dans la doc du package fmt.)

Pour les sorties formatées "simples", le package fmt peut être OK. Pour les documents de sortie complexes, pensez à utiliser text/template (pour le texte général) et html/template (chaque fois que la sortie est HTML).

Passer/remettre http.ResponseWriter

Pour être complet, nous devons mentionner que souvent le contenu que vous souhaitez envoyer en tant que réponse Web est généré par "quelque chose" qui prend en charge le "streaming" du résultat. Un exemple peut être une réponse JSON, qui est générée à partir d'une structure ou d'une carte.

Dans de tels cas, il est souvent plus efficace de passer/remettre votre http.ResponseWriter Qui est un io.Writer À ceci quelque chose s'il prend en charge l'écriture du résultat dans un io.Writer À la volée.

Un bon exemple de cela est la génération de réponses JSON. Bien sûr, vous pouvez marshaler un objet en JSON avec json.Marshal() , qui vous renvoie une tranche d'octets, que vous pouvez simplement envoyer en appelant ResponseWriter.Write().

Cependant, il est plus efficace de faire savoir au package json que vous avez un io.Writer, Et finalement vous voulez envoyer le résultat à cela. De cette façon, il n'est pas nécessaire de générer d'abord le texte JSON dans un tampon, que vous écrivez simplement dans votre réponse, puis jetez. Vous pouvez créer un nouveau json.Encoder en appelant json.NewEncoder() auquel vous pouvez passer votre http.ResponseWriter En tant que io.Writer, Et en appelant Encoder.Encode() après cela écrira directement le résultat JSON dans votre rédacteur de réponse.

Un inconvénient ici est que si la génération de la réponse JSON échoue, vous pouvez avoir une réponse partiellement envoyée/validée que vous ne pouvez pas reprendre. Si c'est un problème pour vous, vous n'avez pas vraiment d'autre choix que de générer la réponse dans un tampon, et si le marshaling réussit, vous pouvez écrire la réponse complète à la fois.

54
icza

Comme vous pouvez le voir sur ici (ResponseWriter) , c'est une interface avec la méthode Write([]byte) (int, error).

Donc, dans io.WriteString Et fmt.Fprintf, Les deux prennent Writer comme 1er argument qui est aussi une méthode enveloppant l'interface Write(p []byte) (n int, err error)

type Writer interface {
    Write(p []byte) (n int, err error)
}

Ainsi, lorsque vous appelez io.WriteString (w, "blah") cochez ici

func WriteString(w Writer, s string) (n int, err error) {
  if sw, ok := w.(stringWriter); ok {
      return sw.WriteString(s)
  }
  return w.Write([]byte(s))
}

ou fmt.Fprintf (w, "blabla") cochez ici

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
   p := newPrinter()
   p.doPrintf(format, a)
   n, err = w.Write(p.buf)
   p.free()
   return
}

vous appelez simplement Write Method indirectement lorsque vous passez la variable ResponseWriter dans les deux méthodes.

Alors pourquoi ne pas l'appeler directement en utilisant w.Write([]byte("blabla\n")). J'espère que vous avez obtenu votre réponse.

PS: il y a aussi une manière différente de l'utiliser, si vous voulez l'envoyer comme réponse JSON.

json.NewEncoder(w).Encode(wrapper)
//Encode take interface as an argument. Wrapper can be:
//wrapper := SuccessResponseWrapper{Success:true, Data:data}
6
hitesh_noty