[PYTHON] Pensez à la nouvelle génération de Rack et WSGI

J'ai pensé à une spécification alternative pour Rack et WSGI (la spécification du protocole, pas la bibliothèque (rack.rb ou wsgiref.py)). Veuillez noter que cela peut ne pas être organisé car je viens d'écrire mes idées.

Je pense que cet article sera révisé plusieurs fois dans le futur. N'hésitez pas à commenter si vous avez des commentaires.

Présentation du rack et WSGI

Ruby Rack et Python WSGI sont des spécifications qui résument les requêtes et réponses HTTP.

Par exemple dans Rack:

class RackApp
  def call(env)    #env est un objet Hash qui représente la demande
    status  = 200                             #Code d'état
    headers = {"Content-Type"=>"text/plain"}  #entête
    body    = "Hello"                         #corps
    return status, headers, [body]   #Ces trois représentent la réponse
  end
end

Les spécifications qui résument les requêtes et réponses HTTP de cette manière sont Rack pour Ruby et WSGI pour Python.

Cela permet aux applications Web d'être utilisées avec n'importe quel serveur d'applications (WEBrick, Unicorn, Puma, UWSGI, serveuse) prenant en charge Rack ou WSGI. Par exemple, vous pouvez facilement basculer entre l'utilisation de WEBrick et de la serveuse, qui sont faciles à utiliser pendant le développement, et l'utilisation rapide de Unicorn, Puma et UWSGI dans un environnement de production.

Rack et WSGI sont également conçus pour faciliter l'ajout de fonctionnalités en utilisant des modèles dits décorateurs. Par exemple

Vous pouvez le faire sans modifier votre application Web.

##Application Rack d'origine
app = RackApp()

##Par exemple, ajoutez une fonctionnalité de session
require 'rack/sesison/cookie'
app = Rack::Session::Cookie.new(app,
        :key => 'rack.session', :path=>'/',
        :expire_after => 3600,
        :secret => '54vYjDUSB0z7NO0ck8ZeylJN0rAX3C')

##Par exemple, afficher les erreurs détaillées uniquement dans un environnement de développement
if ENV['RACK_ENV'] == "development"
  require 'rack/showexceptions'
  app = Rack::ShowExceptions(app)
end

Les objets wrapper permettant d'ajouter des fonctionnalités à l'application Web d'origine de cette manière sont appelés "Middleware" dans Rack et WSGI. Dans l'exemple ci-dessus, Rack :: Session :: Cookie et Rack :: ShowException sont des intergiciels.

Problèmes avec WSGI (Python)

WSGI est la spécification d'origine pour Rack. Sans WSGI, Rack ne serait pas né.

Lorsque WSGI est apparu pour la première fois, il y avait un servlet Java similaire. Cependant, la spécification Servlet était assez compliquée et difficile à implémenter [^ 1]. De plus, en raison de la complexité des spécifications, le comportement peut légèrement différer d'un serveur d'applications à l'autre, donc au final, tout le monde était en état de vérifier les spécifications en exécutant Tomcat, qui est une implémentation de référence, sans regarder les spécifications.

C'est pourquoi WSGI est sorti comme une chose très simple avec des spécifications complètement différentes, bien que je sympathise avec l'idée de Servlet.

[^ 1]: Java et IBM sont bons pour compliquer inutilement les choses.

Regardons le code spécifique. Voici l'exemple de code WSGI.

class WSGIApp(object):

  ##environ est un hachage représentant la requête(dictionnaire)objet
  def __call__(self, environ, start_response):
    status  = "200 OK"      #Des chaînes, pas des nombres
    headers = [             #Liste de clés et de valeurs, pas de hachages
      ('Content-Type', 'text/plain'),
    ]
    start_response(status, headers)   #Lancer une réponse
    return [b"Hello World"]  #Rendre le corps

Si vous regardez cela, vous pouvez voir que c'est assez différent de Rack.

Maintenant, à mon avis, le plus gros problème avec WSGI est l'existence d'une fonction de rappel appelée start_response (). Pour cette raison, les débutants doivent d'abord comprendre «les fonctions qui reçoivent des fonctions (fonctions d'ordre supérieur)» pour comprendre WSGI, qui est un seuil élevé [^ 2].

[^ 2]: Les utilisateurs avancés qui disent: "Vous pouvez facilement comprendre les fonctions d'ordre supérieur" manquent fondamentalement de capacité à comprendre où les débutants trébuchent, ce sont donc des types fonctionnels sans avoir à traiter avec des débutants. Veuillez retourner dans le monde des langues. Pas un grand joueur ou un grand manager. Une personne polyvalente dans le sport n'est pas apte à enseigner les onchi.

L'appel d'une application WSGI est également gaspillé à cause de start_response (). C'est vraiment gênant.

##Si vous ne préparez pas quelque chose comme celui-ci un par un
class StartResponse(object):
  def __call__(self, status, headers):
    self.status = status
    self.headers = headers

##Impossible d'appeler l'application WSGI
app = WSGIApplication()
environ = {'REQUEST_METHOD': 'GET', ...(snip)... }
start_response = StartResponse()
body = app.__call__(environ, start_response)
print(start_response.status)
print(start_response.headers)

(En fait, pour WSGI (PEP-333), une spécification appelée Web3 (PEP-444) qui améliorait ce point a été proposée dans le passé. Dans ce Web3, la fonction de rappel est abolie et elle est similaire à Rack. Il a été conçu pour renvoyer "status, headers, body" à. Personnellement, je m'y attendais, mais il n'a finalement pas été adopté. Je suis désolé.)

WSGI est également un peu ennuyé par le fait que l'en-tête de réponse est une liste de clés et de valeurs au lieu d'un objet de hachage (dictionnaire). C'est parce que vous devez rechercher la liste chaque fois que vous définissez un en-tête.

##Par exemple, si vous avez un en-tête de réponse comme celui-ci
resp_headers = [
  ('Content-Type', "text/html"),
  ('Content-Disposition', "attachment;filename=index.html"),
  ('Content-Encoding', "gzip"),
]
##Vous devez rechercher la liste une par une pour définir la valeur
key = 'Content-Length'
val = str(len(content))
for i, (k, v) in enumerate(resp_headers):
  if k == key:   # or k.tolower() == key.tolower()
    break
else:
  i = -1
if i >= 0:   #Écraser s'il y a
  resp_headers[i] = (key, val)
else:        #Sinon, ajoutez
  resp_headers.append((key, val))

C'est un problème. Ce serait bien de définir une fonction utilitaire dédiée, mais il valait quand même mieux utiliser un objet de hachage (dictionnaire).

##Objet de hachage(Objet dictionnaire)Alors ...
resp_headers = {
  'Content-Type':        "text/html",
  'Content-Disposition': "attachment;filename=index.html",
  'Content-Encoding':    "gzip",
]
##Très facile de définir la valeur!
## (Cependant, on suppose que la casse du nom de clé est unifiée.)
resp_headers['Content-Length'] = str(len(content))

Problèmes avec Rack (Ruby)

Rack (Ruby) est une spécification déterminée en référence à WSGI (Python). Rack est très similaire à WSGI, mais a été amélioré pour être plus simple.

class RackApp
  def call(env)   #env est un objet de hachage qui représente la demande
    status  = 200
    headers = {
      'Content-Type' => 'text/plain;charset=utf-8',
    }
    body    = "Hello World"
    return status, headers, [body]  #Ces trois représentent la réponse
  end
end

Les différences spécifiques sont les suivantes.

Désormais, dans Rack, l'en-tête de réponse est représenté par un objet de hachage. Dans ce cas, qu'en est-il des en-têtes qui peuvent apparaître plusieurs fois, tels que «Set-Cookie»?

Dans Spécifications du rack, il y a la description suivante.

The values of the header must be Strings, consisting of lines (for multiple header values, e.g. multiple Set-Cookie values) separated by "\n".

En d'autres termes, si la valeur de l'en-tête est une chaîne multiligne, on considère que l'en-tête est apparu plusieurs fois.

Mais qu'en est-il de cette spécification? C'est parce que nous devons savoir si chaque en-tête de réponse contient un caractère de rupture. Cela réduira les performances.

headers.each do |k, v|
  v.split(/\n/).each do |s|   #← Double boucle;-(
    puts "#{k}: #{s}"
  end
end

Plutôt que cela, la spécification selon laquelle "l'en-tête qui apparaît plusieurs fois fait de la valeur un tableau" semble être meilleure.

headers.each do |k, v|
  if v.is_a?(Array)     #← C'est mieux
    v.each {|s| puts "#{k}: #{s}" }
  else
    puts "#{k}: #{v}"
  end
end

Vous pouvez également traiter uniquement l'en-tête Set-Cookie. Le seul en-tête qui peut apparaître plusieurs fois est Set-Cookie [^ 3], donc cette spécification n'est pas mauvaise non plus.

set_cookie = "Set-Cookie"
headers.each do |k, v|
  if k == set_cookie     # ← Set-Traitement spécial uniquement pour les cookies
    v.split(/\n/).each {|s| puts "#{k}: #{s}" }
  else
    puts "#{k}: #{v}"
  end
end

[^ 3]: Je pense qu'il y avait un autre en-tête Via, mais ce n'est pas couvert dans la catégorie Rack ou WSGI, donc vous ne devriez considérer que Set-Cooki.

Un autre point concerne la méthode close () 'du corps de la réponse. Les spécifications Rack et WSGI spécifient que si l'objet corps de la réponse a une méthode appelée close (), le serveur d'application appellera close ()` lorsque la réponse au client est terminée. Il s'agit d'une spécification supposant principalement que le corps de la réponse est un objet File.

  def call(env)
    filename = "logo.png "
    headers = {'Content-Type'   => "image/png",
               'Content-Length' => File.size(filename).to_s}
    ##Ouvrez le fichier
    body = File.open(filename, 'rb')
    ##Le fichier ouvert sera envoyé par le serveur d'application lorsque la réponse sera terminée.
    ##Fermer automatiquement()Est appelé
    return [200, headers, body]
  end

Mais il semble que tout ce que vous ayez à faire est de fermer le fichier à la fin de la méthode ʻeach () `.

class AutoClose
  def initialize(file)
    @file = file
  end
  def each
    ##Ce n'est pas efficace car il est lu ligne par ligne
    #@file.each |line|
    #  yield line
    #end
    ##Il est plus efficace de lire dans une taille plus grande
    while (s = @file.read(8192))
      yield s
    end
  ensure            #Si vous lisez tous les fichiers ou s'il y a une erreur
    @file.close()   #Fermer automatiquement
  end
end

Cette spécification à appeler s'il y a une méthode close () peut être nécessaire dans le cas où la méthode ʻeach () du corps de la réponse n'est jamais appelée. Personnellement, je pense que j'aurais dû envisager une spécification de nettoyage comme teardown ()` dans xUnit plutôt qu'une spécification "penser uniquement à l'objet File" (bien que). Je n'ai pas non plus une bonne idée).

A propos des objets Environnement

Dans Rack et WSGI, les requêtes HTTP sont représentées sous forme d'objets de hachage (dictionnaire). C'est ce qu'on appelle l'environnement dans le rack et les spécifications WSGI.

Voyons à quoi cela ressemble.

## Filename: sample1.ru

require 'rack'

class SampleApp
  ## Inspect Environment data
  def call(env)
    status = 200
    headers = {'Content-Type' => "text/plain;charset=utf-8"}
    body = env.map {|k, v| "%-25s: %s\n" % [k.inspect, v.inspect] }.join()
    return status, headers, [body]
  end
end

app = SampleApp.new

run app

Quand j'ai exécuté ceci avec rackup sample1.ru -E production -s puma -p 9292 et que j'ai accédé à http: // localhost: 9292 / index? X = 1 dans mon navigateur, j'ai obtenu le résultat suivant, par exemple. C'est le contenu de l'environnement.

"rack.version"           : [1, 3]
"rack.errors"            : #<IO:<STDERR>>
"rack.multithread"       : true
"rack.multiprocess"      : false
"rack.run_once"          : false
"SCRIPT_NAME"            : ""
"QUERY_STRING"           : "x=1"
"SERVER_PROTOCOL"        : "HTTP/1.1"
"SERVER_SOFTWARE"        : "2.15.3"
"GATEWAY_INTERFACE"      : "CGI/1.2"
"REQUEST_METHOD"         : "GET"
"REQUEST_PATH"           : "/index"
"REQUEST_URI"            : "/index?x=1"
"HTTP_VERSION"           : "HTTP/1.1"
"HTTP_HOST"              : "localhost:9292"
"HTTP_CACHE_CONTROL"     : "max-age=0"
"HTTP_COOKIE"            : "_ga=GA1.1.1305719166.1445760613"
"HTTP_CONNECTION"        : "keep-alive"
"HTTP_ACCEPT"            : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
"HTTP_USER_AGENT"        : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9"
"HTTP_ACCEPT_LANGUAGE"   : "ja-jp"
"HTTP_ACCEPT_ENCODING"   : "gzip, deflate"
"HTTP_DNT"               : "1"
"SERVER_NAME"            : "localhost"
"SERVER_PORT"            : "9292"
"PATH_INFO"              : "/index"
"REMOTE_ADDR"            : "::1"
"puma.socket"            : #<TCPSocket:fd 14>
"rack.hijack?"           : true
"rack.hijack"            : #<Puma::Client:0x3fd60649ac48 @ready=true>
"rack.input"             : #<Puma::NullIO:0x007fac0c896060>
"rack.url_scheme"        : "http"
"rack.after_reply"       : []

(rack.hijack est une nouvelle fonctionnalité introduite dans Rack 1.5. Pour plus d'informations, cliquez ici.)

Cet environnement contient trois types de données.

L'environnement est une collection de ces éléments. Personnellement, je n'aime pas ce genre de spécification, et j'aimerais que vous sépariez au moins l'en-tête de la requête du reste.

La raison de cette spécification est qu'elle est basée sur la spécification CGI. Je ne pense pas que les jeunes d'aujourd'hui connaissent CGI, mais c'est pourquoi il était très souvent utilisé dans le passé. WSGI a emprunté cette spécification CGI pour déterminer la spécification d'environnement, et Rack en hérite. Par conséquent, cela peut sembler étrange à ceux qui ne connaissent pas CGI. Quelqu'un pourrait dire: "Pourquoi l'en-tête User-Agent a-t-il été remplacé par HTTP_USER_AGENT? Je devrais simplement utiliser la chaîne User-Agent."

Problèmes avec les objets Environnement

Comme nous l'avons déjà vu, un objet Environment est un objet de hachage contenant des dizaines d'éléments.

Du point de vue des performances, créer un objet de hachage avec des dizaines d'éléments n'est pas souhaitable car il est assez coûteux à utiliser en Ruby et Python. Par exemple, avec Keight.rb, un framework 100 fois plus rapide que Ruby on Rails, ** la génération d'un objet Environment peut prendre plus de temps que le traitement d'une requête **.

Vérifions-le avec un script de référence.

# -*- coding: utf-8 -*-
require 'rack'
require 'keight'
require 'benchmark/ips'

##Classe d'action(Contrôleur dans MVC)Créer
class API < K8::Action
  mapping '/hello',  :GET=>:say_hello
  def say_hello()
    return "<h1>Hello, World!</h1>"
  end
end

##Créer une application Rack et attribuer une classe d'action
mapping = [
    ['/api',   API],
]
rack_app = K8::RackApplication.new(mapping)

##Exemple d'exécution
expected = [
  200,
  {"Content-Length"=>"22", "Content-Type"=>"text/html; charset=utf-8"},
  ["<h1>Hello, World!</h1>"]
]
actual = rack_app.call(Rack::MockRequest.env_for("/api/hello"))
actual == expected  or raise "assertion failed"

## GET /api/Objet d'environnement qui représente bonjour
env = Rack::MockRequest.env_for("/api/hello")

##référence
Benchmark.ips do |x|
  x.config(:time => 5, :warmup => 1)

  ##Créer un nouvel objet Environnement(faire une copie)
  x.report("just copy env") do |n|
    i = 0
    while (i += 1) <= n
      env.dup()
    end
  end

  ##Créer un objet Environnement pour gérer la demande
  x.report("Keight (copy env)") do |n|
    i = 0
    while (i += 1) <= n
      actual = rack_app.call(env.dup)
    end
    actual == expected  or raise "assertion failed"
  end

  ##Réutiliser les objets d'environnement pour gérer les demandes
  x.report("Keight (reuse env)") do |n|
    i = 0
    while (i += 1) <= n
      actual = rack_app.call(env)
    end
    actual == expected  or raise "assertion failed"
  end

  x.compare!
end

Lorsque j'ai exécuté ceci, j'ai obtenu les résultats suivants, par exemple (Ruby 2.3, Keight.rb 0.2, OSX El Capitan):

Calculating -------------------------------------
       just copy env    12.910k i/100ms
   Keight (copy env)     5.523k i/100ms
  Keight (reuse env)    12.390k i/100ms
-------------------------------------------------
       just copy env    147.818k (± 8.0%) i/s -    735.870k
   Keight (copy env)     76.103k (± 4.4%) i/s -    381.087k
  Keight (reuse env)    183.065k (± 4.8%) i/s -    916.860k

Comparison:
  Keight (reuse env):   183064.5 i/s
       just copy env:   147818.2 i/s - 1.24x slower
   Keight (copy env):    76102.8 i/s - 2.41x slower

À partir des trois dernières lignes, nous pouvons voir que:

Dans cette situation, accélérer davantage le cadre ne rendra pas l'application beaucoup plus rapide. Pour surmonter cette impasse, il semble bon d'améliorer les spécifications du Rack elles-mêmes.

Problèmes avec les motifs décorateurs

(TODO)

Pensez à la nouvelle génération de Rack et WSGI

Eh bien, entrez enfin dans le sujet principal.

Afin de résoudre les problèmes décrits jusqu'à présent, j'ai pensé à une alternative aux actuels Rack et WSGI. Soi-disant, "Mes pensées sur Saikyo no Raku".

La nouvelle spécification ne change pas l'abstraction des requêtes et réponses HTTP. Je vais donc me concentrer sur la façon d'abstraire ces deux.

De plus, le Rack et le WSGI actuels héritent partiellement des spécifications CGI. Cependant, CGI est une spécification à l'ancienne qui suppose que les données sont transmises via des variables d'environnement. Il ne convient pas à cette époque, vous pouvez donc oublier les spécifications CGI.

Requête HTTP

Les requêtes HTTP sont divisées en éléments suivants:

La méthode de demande peut être une chaîne supérieure ou un symbole. Le symbole semble être meilleur en termes de performances.

meth = :GET

Le chemin de la demande peut être une chaîne. Rack doit prendre en compte SCRIPT_NAME ainsi que PATH_INFO, mais maintenant que personne n'utilisera SCRIPT_NAME, nous allons simplement considérer l'équivalent de PATH_INFO.

path = "/index.html"

L'en-tête de la demande peut être un objet de hachage. De plus, je ne veux pas convertir de User-Agent en HTTP_USER_AGENT, mais HTTP / 2 semble avoir des noms d'en-tête inférieurs, donc je vais probablement le faire correspondre.

headers = {
  "host"       => "www.example.com",
  "user-agent" => "Mozilla/5.0 ....(snip)....",
  ....(snip)....,
}

Le paramètre de requête est «nil» ou une chaîne. S'il n'y a pas de «?», Cela devient «nil», et s'il existe, cela devient une chaîne de caractères (il peut s'agir d'un caractère vide).

query = "x=1"

Les E / S liées (rack.input et rack.errors et rack.hijack ou puma.socket) doivent être dans un seul tableau. Ce ne sont que les équivalents de stdin, stderr et stdout ... n'est-ce pas? Peut-être que socket sert aussi de rack.input, mais je ne suis pas familier avec cela, donc je vais le séparer ici.

ios = [
  StringIO.new(),   # rack.input
  $stderr,          # rack.errors
  puma_socket,
]

La valeur des autres informations de demande change pour chaque demande. Cela devrait être un objet de hachage.

options = {
  http:       "1.1",    # HTTP_VERSION
  client:     "::1",    # REMOTE_ADDR
  protocol:   "http",   # rack.url_scheme
}

Les dernières informations du serveur ne doivent pas changer à moins que le serveur d'applications n'ait changé. Ainsi, une fois que vous l'avez créé en tant qu'objet de hachage, vous pouvez le réutiliser.

server = {
  name:  "localhost".freeze,    # SERVER_NAME
  port:  "9292".freeze,         # SERVER_PORT
  'rack.version':       [1, 3].freeze,
  'rack.multithread':   true,
  'rack.multiprocess':  false,
  'rack.run_once':      false,
}.freeze

Considérez une application Rack qui les reçoit.

class RackApp
  def call(meth, path, headers, query, ios, options, server)
    input, errors, socket = ios
    ...
  end
end

Wow, il a 7 arguments. C'est un peu cool, non? Les trois premiers (meth, chemin et en-têtes) sont au cœur de la requête, donc en les laissant seuls en tant qu'arguments, la requête et ios sont susceptibles d'être regroupés en options.

options = {
  query:    "x=1",     # QUERY_STRING
  #
  input:    StringIO.new,   # rack.input,
  error:    $stderr,        # rack.erros,
  socket:   puma_socket,    # rack.hijack or puma.socket
  #
  http:     "1.1",     # HTTP_VERSION
  client:   "::1",     # REMOTE_ADDR
  protocol: "http",    # rack.url_scheme
}

Cela réduira le nombre d'arguments de sept à cinq.

class RackApp
  def call(meth, path, headers, options, server)
    query  = options[:query]
    input  = options[:input]
    error  = options[:error]
    socket = options[:socket]   # or :output ?
    ...
  end
end

Eh bien, je pense que c'est correct d'utiliser ça.

Réponse HTTP

La réponse HTTP peut toujours être représentée par l'état, l'en-tête et le corps.

  def call(meth, path, headers, options, server)
    status  = 200
    headers = {"content-type"=>"application/json"},
    body    = '{"message":"Hello!"}'
    return status, headers, body
  end

Cependant, je pense que l'en-tête Content-Type peut être traité spécialement. Parce que dans les applications Rack actuelles, il n'y a que des en-têtes Content-Type, tels que {" Content-Type "=>" text / html} " et {" Content-Type "=>" application / json "}. En effet, dans de nombreux cas, il n'est pas inclus. Par conséquent, si seul Content-Type est traité spécialement et rendu indépendant, ce sera un peu plus simple.

  def call(meth, path, headers, options, server)
    ##Que ça
    return 200, {"Content-Type"=>"text/plain"}, ["Hello"]
    ##C'est plus concis
    return 200, "text/plain", {}, ["Hello"]
  end

Il y a aussi d'autres problèmes.

Le statut est-il un entier ou une chaîne? Je pense que l'entier
est bien, mais ce serait bien s'il y avait un moyen de spécifier un statut personnalisé. Cependant, ce n'est pas une spécification Rack, et je pense que ce serait bien s'il y avait un moyen de s'inscrire pour chaque serveur d'application.
L'en-tête est-il un hachage ou une liste?
Cela devrait plus être un hachage.
Et s'il y a plusieurs en-têtes Set-Cookie?
Cela devrait autoriser un tableau de chaînes dans les valeurs d'en-tête, comme déjà expliqué. Et décidez que la valeur d'en-tête ne doit pas contenir de caractères de rupture.
S'il faut autoriser les chaînes dans le corps?
La spécification Rack actuelle ne vous permet pas de spécifier une chaîne directement sur le corps, car le corps doit implémenter la méthode ʻeach () `. Au lieu de cela, il est standard de spécifier un tableau de chaînes.

Cependant, comme la plupart des réponses renvoient une chaîne en tant que corps, il est inutile de l'envelopper dans un tableau un par un. Si possible, le corps doit être une "chaîne, ou un objet qui renvoie une chaîne avec ʻeach ()` ". </ jj>

Dois-je appeler la méthode `close ()` du corps lorsque la réponse est complète?
C'est un problème difficile. Comme mentionné précédemment, cette spécification n'est pas requise tant qu'il est garanti que la méthode ʻeach () `sera appelée. Cependant, il n'y a pas de telle garantie, donc cela garantirait d'appeler `close ()` à la place.

Mais ce qui est vraiment souhaitable, c'est d'avoir l'équivalent de teardown (). C'est dommage que je ne puisse penser à aucune spécification spécifique [^ 4]. </ jj>

[^ 4]: Je pensais que rack.after_reply était cela, mais cela semble être une fonction unique de Puma.

Motif décorateur

(TODO)

E / S pilotées par événement et non bloquantes

(TODO)

Prise en charge HTTP / 2

(TODO)

en conclusion

Nous aimerions entendre les opinions d'experts.

Les références

Lié au WSGI

  • PEP-0333 -- Python Web Server Gateway Interface v1.0.1
  • https://www.python.org/dev/peps/pep-3333/ (version révisée pour Python3)
  • https://www.python.org/dev/peps/pep-0333/ (original)
  • Spécifications WSGI. Tout a commencé ici.
  • PEP-0444 -- Python Web3 Interface
    • https://www.python.org/dev/peps/pep-0444/#values-returned-by-a-web3-application
  • Cahier des charges proposé pour réussir WSGI. Malheureusement, il n'a pas été adopté.

En relation avec le rack

  • Rack: a Ruby Webserver Interface
  • http://rack.github.io/ (site Web)
  • http://rubydoc.info/github/rack/rack/master/file/SPEC
  • Spécifications récentes. Il existe également une description de l'API de piratage.
  • Rubyist Magazine: spécification du rack (traduction)
  • http://magazine.rubyist.net/?0033-TranslationArticle
  • Notez qu'il est ancien car il s'agit de l'ère Rack 1.1.
  • À propos de la nouvelle fonctionnalité "Hijack API" de Rack 1.5
  • http://kwatch.houkagoteatime.net/blog/2013/01/25/rack-hijacking-api/
  • Pour être honnête, je me demande quelle est cette spécification.

Liens connexes

Juste sur la liste de diffusion de Rack J'ai une question sur le support HTTP2. Il y a eu une petite discussion sur Rack2 à ce sujet, alors j'ai traversé diverses choses.

  • Wardrop/Rack-Next: For discussion and planning of the next ruby web interface.
    • https://github.com/Wardrop/Rack-Next
  • Attentes pour la prochaine version de Rack. Je le lirai plus tard.
  • Rack 2.0, or Rack for the Future
    • https://gist.github.com/raggi/11c3491561802e573a47
  • Répertorie les avantages et les inconvénients de Rack.
  • the_metal: A spike for thoughts about Rack 2.0
    • https://github.com/tenderlove/the_metal
  • Une expérience réalisée par l'auteur de Nokogiri, tendrelove pour déterminer les spécifications minimales pour les objets Request et Response. Il y a une opinion intéressante sur Issue.
  • halorgium/rack2: async hax for rack
    • https://github.com/halorgium/rack2
  • Tentatives de rendre Rack asynchrone?

Recommended Posts