[PYTHON] Denken Sie an das Rack und WSGI der nächsten Generation

Ich dachte an eine alternative Spezifikation für Rack und WSGI (die Protokollspezifikation, nicht die Bibliothek (Rack.rb oder wsgiref.py)). Bitte beachten Sie, dass es möglicherweise nicht organisiert ist, weil ich gerade meine Ideen aufgeschrieben habe.

Ich denke, dieser Artikel wird in Zukunft mehrmals überarbeitet. Fühlen Sie sich frei zu kommentieren, wenn Sie irgendwelche Kommentare haben.

Rack- und WSGI-Übersicht

Rubys Rack und Pythons WSGI sind Spezifikationen, die HTTP-Anforderungen und -Antworten abstrahieren.

Zum Beispiel in Rack:

class RackApp
  def call(env)    #env ist ein Hash-Objekt, das die Anforderung darstellt
    status  = 200                             #Statuscode
    headers = {"Content-Type"=>"text/plain"}  #Header
    body    = "Hello"                         #Körper
    return status, headers, [body]   #Diese drei repräsentieren die Antwort
  end
end

Die Spezifikationen, die HTTP-Anforderungen und -Antworten auf diese Weise abstrahieren, sind Rack für Ruby und WSGI für Python.

Auf diese Weise können Webanwendungen mit jedem Anwendungsserver (WEBrick, Unicorn, Puma, UWSGI, Kellnerin) verwendet werden, der Rack oder WSGI unterstützt. Sie können beispielsweise problemlos zwischen der Verwendung von WEBrick und der Kellnerin, die während der Entwicklung einfach zu verwenden sind, und der Verwendung von Unicorn, Puma und UWSGI in einer Produktionsumgebung wechseln.

Rack und WSGI wurden auch entwickelt, um das Hinzufügen von Funktionen mithilfe von sogenannten Dekorationsmustern zu vereinfachen. Zum Beispiel

Sie können dies tun, ohne Ihre Webanwendung zu ändern.

##Original Rack-Anwendung
app = RackApp()

##Fügen Sie beispielsweise Sitzungsfunktionen hinzu
require 'rack/sesison/cookie'
app = Rack::Session::Cookie.new(app,
        :key => 'rack.session', :path=>'/',
        :expire_after => 3600,
        :secret => '54vYjDUSB0z7NO0ck8ZeylJN0rAX3C')

##Zeigen Sie beispielsweise detaillierte Fehler nur in einer Entwicklungsumgebung an
if ENV['RACK_ENV'] == "development"
  require 'rack/showexceptions'
  app = Rack::ShowExceptions(app)
end

Wrapper-Objekte zum Hinzufügen von Funktionen zur ursprünglichen Webanwendung auf diese Weise werden in Rack und WSGI als "Middleware" bezeichnet. Im obigen Beispiel sind "Rack :: Session :: Cookie" und "Rack :: ShowException" Middleware.

Probleme mit WSGI (Python)

WSGI ist die ursprüngliche Spezifikation für Rack. Ohne WSGI wäre Rack nicht geboren worden.

Als WSGI zum ersten Mal erschien, gab es ein ähnliches Java-Servlet. Die Servlet-Spezifikationen waren jedoch recht komplex und schwer zu implementieren [^ 1]. Aufgrund der Komplexität der Spezifikationen kann sich das Verhalten von Anwendungsserver zu Anwendungsserver geringfügig unterscheiden. Letztendlich war jeder in der Lage, die Spezifikationen durch Ausführen von Tomcat, einer Referenzimplementierung, zu überprüfen, ohne die Spezifikationen zu berücksichtigen.

Deshalb kam WSGI als sehr einfache Sache mit völlig anderen Spezifikationen heraus, obwohl ich mit der Idee von Servlet sympathisiere.

[^ 1]: Java und IBM sind gut darin, Dinge unnötig kompliziert zu machen.

Schauen wir uns den spezifischen Code an. Unten finden Sie den WSGI-Beispielcode.

class WSGIApp(object):

  ##environ ist ein Hash, der die Anforderung darstellt(Wörterbuch)Objekt
  def __call__(self, environ, start_response):
    status  = "200 OK"      #Zeichenfolgen, keine Zahlen
    headers = [             #Liste der Schlüssel und Werte, keine Hashes
      ('Content-Type', 'text/plain'),
    ]
    start_response(status, headers)   #Starten Sie eine Antwort
    return [b"Hello World"]  #Gib den Körper zurück

Wenn Sie sich das ansehen, können Sie sehen, dass es ganz anders ist als Rack.

Meiner Meinung nach ist das größte Problem bei WSGI die Existenz einer Rückruffunktion namens "start_response ()". Aus diesem Grund müssen Anfänger zuerst "Funktionen, die Funktionen empfangen (Funktionen höherer Ordnung)" verstehen, um WSGI zu verstehen, das eine hohe Schwelle darstellt [^ 2].

[^ 2]: Fortgeschrittene Benutzer, die sagen: "Sie können Funktionen höherer Ordnung leicht verstehen", sind grundsätzlich nicht in der Lage zu verstehen, wo Anfänger stolpern. Sie sind also Funktionstypen, ohne sich mit Anfängern befassen zu müssen. Bitte kehren Sie in die Welt der Sprachen zurück. Kein großartiger Spieler oder Manager. Eine Person, die vielseitig im Sport ist, ist nicht für das Unterrichten von Onchi-Übungen geeignet.

Das Aufrufen einer WSGI-Anwendung wird auch wegen start_response () verschwendet. Das ist wirklich mühsam.

##Wenn Sie so etwas nicht einzeln vorbereiten
class StartResponse(object):
  def __call__(self, status, headers):
    self.status = status
    self.headers = headers

##WSGI-Anwendung kann nicht aufgerufen werden
app = WSGIApplication()
environ = {'REQUEST_METHOD': 'GET', ...(snip)... }
start_response = StartResponse()
body = app.__call__(environ, start_response)
print(start_response.status)
print(start_response.headers)

(Tatsächlich wurde für WSGI (PEP-333) in der Vergangenheit eine Spezifikation namens Web3 (PEP-444) vorgeschlagen, die diesen Punkt verbessert. In diesem Web3 wurde die Rückruffunktion abgeschafft und ähnelt Rack. Es wurde entwickelt, um "Status, Header, Body" zurückzugeben. Ich persönlich habe es erwartet, aber es wurde am Ende nicht übernommen. Es tut mir leid.)

Bei WSGI ist es außerdem etwas ärgerlich, dass der Antwortheader eine Liste von Schlüsseln und Werten anstelle eines Hash-Objekts (Wörterbuchs) enthält. Das liegt daran, dass Sie die Liste jedes Mal durchsuchen müssen, wenn Sie einen Header festlegen.

##Zum Beispiel, wenn Sie einen solchen Antwortheader haben
resp_headers = [
  ('Content-Type', "text/html"),
  ('Content-Disposition', "attachment;filename=index.html"),
  ('Content-Encoding', "gzip"),
]
##Sie müssen die Liste einzeln durchsuchen, um den Wert festzulegen
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:   #Überschreiben, falls vorhanden
  resp_headers[i] = (key, val)
else:        #Wenn nicht, fügen Sie hinzu
  resp_headers.append((key, val))

Das ist ein Ärger. Es wäre schön, eine dedizierte Dienstprogrammfunktion zu definieren, aber es war trotzdem besser, ein Hash-Objekt (Wörterbuch) zu verwenden.

##Hash-Objekt(Wörterbuchobjekt)Dann ...
resp_headers = {
  'Content-Type':        "text/html",
  'Content-Disposition': "attachment;filename=index.html",
  'Content-Encoding':    "gzip",
]
##Sehr einfach, den Wert einzustellen!
## (Es wird jedoch davon ausgegangen, dass der Fall des Schlüsselnamens einheitlich ist.)
resp_headers['Content-Length'] = str(len(content))

Probleme mit Rack (Ruby)

Rack (Ruby) ist eine Spezifikation, die unter Bezugnahme auf WSGI (Python) festgelegt wurde. Das Rack ist dem WSGI sehr ähnlich, wurde jedoch verbessert, um es einfacher zu machen.

class RackApp
  def call(env)   #env ist ein Hash-Objekt, das die Anforderung darstellt
    status  = 200
    headers = {
      'Content-Type' => 'text/plain;charset=utf-8',
    }
    body    = "Hello World"
    return status, headers, [body]  #Diese drei repräsentieren die Antwort
  end
end

Die spezifischen Unterschiede sind wie folgt.

In Rack wird der Antwortheader nun durch ein Hash-Objekt dargestellt. Was ist in diesem Fall mit Headern, die mehrfach angezeigt werden können, z. B. "Set-Cookie"?

In Rack-Spezifikationen gibt es die folgende Beschreibung.

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

Mit anderen Worten, wenn der Wert des Headers eine mehrzeilige Zeichenfolge ist, wird davon ausgegangen, dass der Header mehrmals angezeigt wurde.

Aber was ist mit dieser Spezifikation? Das liegt daran, dass wir herausfinden müssen, ob jeder Antwortheader ein Unterbrechungszeichen enthält. Dies verringert die Leistung.

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

Stattdessen scheint die Angabe, dass "mehrfach angezeigter Header den Wert zu einem Array macht", besser zu sein.

headers.each do |k, v|
  if v.is_a?(Array)     #← Das ist besser
    v.each {|s| puts "#{k}: #{s}" }
  else
    puts "#{k}: #{v}"
  end
end

Alternativ können Sie nur den Set-Cookie-Header speziell behandeln. Der einzige Header, der mehrmals angezeigt werden kann, ist Set-Cookie [^ 3], daher ist diese Spezifikation auch nicht schlecht.

set_cookie = "Set-Cookie"
headers.each do |k, v|
  if k == set_cookie     # ← Set-Sonderbehandlung nur für Kekse
    v.split(/\n/).each {|s| puts "#{k}: #{s}" }
  else
    puts "#{k}: #{v}"
  end
end

[^ 3]: Ich denke, es gab einen anderen Via-Header, der jedoch nicht in der Kategorie Rack oder WSGI behandelt wird. Sie sollten daher nur Set-Cooki in Betracht ziehen.

Ein weiterer Punkt betrifft die Methode close () des Antwortkörpers. Die Rack- und WSGI-Spezifikationen geben an, dass der Anwendungsserver "close ()" aufruft, wenn ein Antworttextobjekt eine Methode namens "close ()" hat, wenn die Antwort auf den Client abgeschlossen ist. Dies ist eine Spezifikation, die hauptsächlich davon ausgeht, dass der Antworttext ein Dateiobjekt ist.

  def call(env)
    filename = "logo.png "
    headers = {'Content-Type'   => "image/png",
               'Content-Length' => File.size(filename).to_s}
    ##Öffne die Datei
    body = File.open(filename, 'rb')
    ##Die geöffnete Datei wird vom App-Server gesendet, wenn die Antwort abgeschlossen ist.
    ##Automatisch schließen()Wird genannt
    return [200, headers, body]
  end

Dies scheint jedoch alles zu sein, was Sie tun müssen, ist die Datei am Ende der each () -Methode zu schließen.

class AutoClose
  def initialize(file)
    @file = file
  end
  def each
    ##Dies ist nicht effizient, da es Zeile für Zeile gelesen wird
    #@file.each |line|
    #  yield line
    #end
    ##Es ist effizienter, in einer größeren Größe zu lesen
    while (s = @file.read(8192))
      yield s
    end
  ensure            #Wenn Sie alle Dateien gelesen haben oder wenn ein Fehler vorliegt
    @file.close()   #Automatisch schließen
  end
end

Diese Spezifikation des Aufrufs, wenn es eine "close ()" - Methode gibt, kann in Fällen erforderlich sein, in denen die "each ()" - Methode des Antwortkörpers niemals aufgerufen wird. Persönlich denke ich, ich hätte über die Bereinigungsspezifikationen wie teardown () in xUnit nachdenken sollen, anstatt über die Spezifikationen "Ich denke nur an Dateiobjekte" (obwohl). Ich habe auch keine gute Idee.

Informationen zu Umgebungsobjekten

Sowohl in Rack als auch in WSGI werden HTTP-Anforderungen als Hash-Objekte (Wörterbuchobjekte) dargestellt. Dies wird in den Rack- und WSGI-Spezifikationen als Umgebung bezeichnet.

Mal sehen, wie das aussieht.

## 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

Als ich dies mit rampup sample1.ru -E Production -s puma -p 9292 ausführte und in meinem Browser auf http: // localhost: 9292 / index? X = 1 zugegriffen habe, habe ich beispielsweise das folgende Ergebnis erhalten. Dies ist der Inhalt der Umgebung.

"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 ist eine neue Funktion, die in Rack 1.5 eingeführt wurde. Weitere Informationen finden Sie hier.)

Diese Umgebung enthält drei Arten von Daten.

Umwelt ist eine Sammlung dieser Elemente. Persönlich mag ich diese Art von Spezifikation nicht und ich möchte, dass Sie zumindest den Anforderungsheader und den Rest trennen.

Der Grund für diese Spezifikation ist, dass sie auf der CGI-Spezifikation basiert. Ich glaube nicht, dass junge Leute heute etwas über CGI wissen, aber deshalb wurde es in der Vergangenheit sehr oft verwendet. WSGI hat diese CGI-Spezifikation ausgeliehen, um die Umgebungsspezifikation zu bestimmen, und Rack erbt sie. Daher kann es für diejenigen, die CGI nicht kennen, seltsam aussehen. Möglicherweise erhalten Sie den Eindruck "Warum wird der User-Agent-Header in HTTP_USER_AGENT geändert? Sie können einfach die User-Agent-Zeichenfolge verwenden."

Probleme mit Umgebungsobjekten

Wie wir bereits gesehen haben, ist ein Umgebungsobjekt ein Hash-Objekt, das Dutzende von Elementen enthält.

Unter Leistungsgesichtspunkten ist das Erstellen eines Hash-Objekts mit Dutzenden von Elementen nicht wünschenswert, da der Betrieb in Ruby und Python recht teuer ist. Mit Keight.rb, einem Framework, das 100-mal schneller als Ruby on Rails ist, ** kann das Generieren eines Umgebungsobjekts ** möglicherweise länger dauern als das Verarbeiten einer Anforderung **.

Lassen Sie es uns tatsächlich mit einem Benchmark-Skript überprüfen.

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

##Aktionsklasse(Controller in MVC)Erstellen
class API < K8::Action
  mapping '/hello',  :GET=>:say_hello
  def say_hello()
    return "<h1>Hello, World!</h1>"
  end
end

##Erstellen Sie eine Rack-Anwendung und weisen Sie eine Aktionsklasse zu
mapping = [
    ['/api',   API],
]
rack_app = K8::RackApplication.new(mapping)

##Ausführungsbeispiel
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/Umgebungsobjekt, das Hallo darstellt
env = Rack::MockRequest.env_for("/api/hello")

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

  ##Erstellen Sie ein neues Umgebungsobjekt(eine Kopie machen)
  x.report("just copy env") do |n|
    i = 0
    while (i += 1) <= n
      env.dup()
    end
  end

  ##Erstellen Sie ein Umgebungsobjekt, um die Anforderung zu verarbeiten
  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

  ##Verwenden Sie Umgebungsobjekte erneut, um Anforderungen zu verarbeiten
  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

Als ich dies ausführte, erhielt ich zum Beispiel die folgenden Ergebnisse (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

Aus den letzten drei Zeilen können wir Folgendes ersehen:

In dieser Situation wird die Anwendung durch eine weitere Beschleunigung des Frameworks nicht viel schneller. Um diesen Deadlock zu überwinden, sollten die Rack-Spezifikationen selbst verbessert werden.

Probleme mit Dekorationsmustern

(TODO)

Denken Sie an das Rack und WSGI der nächsten Generation

Nun, kommen Sie endlich zum Hauptthema.

Um die Probleme zu lösen, die ich bisher beschrieben habe, habe ich eine Alternative zum aktuellen Rack und WSGI in Betracht gezogen. Sogenannt "Meine Gedanken zu Saikyo no Raku".

Die neue Spezifikation ändert nichts an der Abstraktion von HTTP-Anforderungen und -Antworten. Also werde ich mich darauf konzentrieren, wie man diese beiden abstrahiert.

Außerdem erben das aktuelle Rack und WSGI teilweise die CGI-Spezifikationen. CGI ist jedoch eine altmodische Spezifikation, bei der davon ausgegangen wird, dass Daten über Umgebungsvariablen übertragen werden. Es ist nicht für diese Ära geeignet, daher können Sie die CGI-Spezifikationen vergessen.

HTTP-Anfrage

HTTP-Anforderungen sind in folgende Elemente unterteilt:

Die Anforderungsmethode kann eine obere Zeichenfolge oder ein Symbol sein. Das Symbol scheint in Bezug auf die Leistung besser zu sein.

meth = :GET

Der Anforderungspfad kann eine Zeichenfolge sein. Das Rack muss sowohl SCRIPT_NAME als auch PATH_INFO berücksichtigen, aber jetzt, da niemand SCRIPT_NAME verwenden wird, werden wir nur das Äquivalent von PATH_INFO berücksichtigen.

path = "/index.html"

Der Anforderungsheader kann ein Hash-Objekt sein. Außerdem möchte ich nicht von User-Agent zu HTTP_USER_AGENT konvertieren, aber HTTP / 2 scheint niedrigere Headernamen zu haben, daher werde ich wahrscheinlich damit übereinstimmen.

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

Der Abfrageparameter ist entweder "nil" oder eine Zeichenfolge. Wenn es kein "?" Gibt, wird es zu "null", und wenn es existiert, wird es zu einer Zeichenfolge (es kann ein leeres Zeichen sein).

query = "x=1"

E / A-bezogen (Rack.Eingang und Rack.Fehler und Rack.hijack oder Puma.Socket) sollten sich in einem Array befinden. Dies sind nur die Äquivalente von stdin, stderr und stdout ... nicht wahr? Vielleicht dient der Socket auch als Rack-Eingang, aber ich bin nicht damit vertraut, deshalb werde ich ihn hier trennen.

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

Der Wert anderer Anforderungsinformationen ändert sich für jede Anforderung. Dies sollte ein Hash-Objekt sein.

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

Die letzten Serverinformationen sollten sich nur ändern, wenn sich der Anwendungsserver geändert hat. Sobald Sie es als Hash-Objekt erstellt haben, können Sie es wiederverwenden.

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

Stellen Sie sich eine Rack-Anwendung vor, die diese empfängt.

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

Wow, es hat 7 Argumente. Das ist ein bisschen cool, nicht wahr? Die ersten drei (meth, path und headers) bilden den Kern der Anforderung. Wenn Sie sie als Argumente, Abfragen und ios in Ruhe lassen, werden sie wahrscheinlich in Optionen gruppiert.

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
}

Dadurch wird die Anzahl der Argumente von sieben auf fünf reduziert.

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

Nun, ich denke es ist okay das zu benutzen.

HTTP-Antwort

Die HTTP-Antwort kann weiterhin durch Status, Header und Body dargestellt werden.

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

Ich denke jedoch, dass der Content-Type-Header speziell behandelt werden kann. Denn in heutigen Rack-Anwendungen gibt es nur Content-Type-Header wie "{" Content-Type "=>" text / html} "und" {"Content-Type" => "application / json"} ". Dies liegt daran, dass es viele Fälle gibt, in denen es nicht enthalten ist. Wenn daher nur der Inhaltstyp speziell behandelt und unabhängig gemacht wird, ist dies etwas einfacher.

  def call(meth, path, headers, options, server)
    ##Als das
    return 200, {"Content-Type"=>"text/plain"}, ["Hello"]
    ##Dies ist prägnanter
    return 200, "text/plain", {}, ["Hello"]
  end

Es gibt noch einige andere Probleme.

Ist der Status eine Ganzzahl oder eine Zeichenfolge?
Eine Ganzzahl ist in Ordnung, aber es wäre schön, wenn es eine Möglichkeit gäbe, einen benutzerdefinierten Status anzugeben. Dies ist jedoch keine Rack-Spezifikation, und ich denke, es wäre in Ordnung, wenn es eine Möglichkeit gäbe, sich für jeden Anwendungsserver zu registrieren.
Ist der Header ein Hash oder eine Liste?
Dies sollte kein Hash mehr sein.
Was ist, wenn mehrere Set-Cookie-Header vorhanden sind?
Dies sollte ein Array von Zeichenfolgen in den Header-Werten ermöglichen, wie oben beschrieben. Und entscheiden Sie, dass der Header-Wert keine Unterbrechungszeichen enthalten soll.
Gibt an, ob Zeichenfolgen im Körper zugelassen werden sollen?
In der aktuellen Rack-Spezifikation können Sie keine Zeichenfolge direkt im Body angeben, da der Body die Methode "each ()" implementieren muss. Stattdessen ist es Standard, ein Array von Zeichenfolgen anzugeben.

Da die meisten Antworten einen String als Body zurückgeben, ist es sinnlos, ihn einzeln in ein Array zu packen. Wenn möglich, sollte der Body eine "Zeichenfolge" oder ein Objekt sein, das eine Zeichenfolge mit "each ()" zurückgibt. </ dd>

Soll ich die Methode close () des Körpers aufrufen, wenn die Antwort abgeschlossen ist?
Dies ist ein schwieriges Problem. Wie bereits erwähnt, ist diese Spezifikation nicht erforderlich, solange garantiert ist, dass die Methode "each ()" aufgerufen wird. Es gibt jedoch keine solche Garantie, so dass es garantiert wäre, stattdessen "close ()" aufzurufen.

Aber was wirklich wünschenswert ist, ist das Äquivalent von "teardown ()". Schade, dass mir keine konkreten Spezifikationen einfallen [^ 4]. </ dd>

[^ 4]: Ich dachte, dass "ack.after_reply "das war, aber es scheint eine einzigartige Funktion von Puma zu sein.

Dekorationsmuster

(TODO)

Ereignisgesteuerte und nicht blockierende E / A.

(TODO)

HTTP / 2-Unterstützung

(TODO)

abschließend

Wir würden gerne die Meinungen von Experten hören.

Verweise

WSGI bezogen

  • PEP-0333 -- Python Web Server Gateway Interface v1.0.1
  • https://www.python.org/dev/peps/pep-3333/ (Überarbeitete Version für Python3)
  • https://www.python.org/dev/peps/pep-0333/ (Original)
  • WSGI-Spezifikationen. Hier hat alles angefangen.
  • PEP-0444 -- Python Web3 Interface
    • https://www.python.org/dev/peps/pep-0444/#values-returned-by-a-web3-application
  • Eine Spezifikation, die als Nachfolger der WSGI vorgeschlagen wurde. Leider wurde es nicht übernommen.

Rack bezogen

  • Rack: a Ruby Webserver Interface
  • http://rack.github.io/ (Website)
  • http://rubydoc.info/github/rack/rack/master/file/SPEC
  • Aktuelle Spezifikationen. Es gibt auch eine Beschreibung der Hijack-API.
  • Rubyist Magazine: Rack-Spezifikation (Übersetzung)
  • http://magazine.rubyist.net/?0033-TranslationArticle
  • Beachten Sie, dass es alt ist, weil es die Rack 1.1-Ära ist.
  • Über Rack 1.5 neue Funktion "Hijack API"
  • http://kwatch.houkagoteatime.net/blog/2013/01/25/rack-hijacking-api/
  • Um ehrlich zu sein, frage ich mich, was diese Spezifikation ist.

ähnliche Links

Ich habe gerade eine Frage in Racks Mailingliste zur HTTP2-Unterstützung erhalten (https://groups.google.com/forum/#!topic/rack-devel/_sPwu9vVsYA). Es gab ein kleines Gespräch über Rack2, also habe ich verschiedene Dinge durchgearbeitet.

  • Wardrop/Rack-Next: For discussion and planning of the next ruby web interface.
    • https://github.com/Wardrop/Rack-Next
  • Erwartungen für die nächste Version von Rack. Ich werde es später lesen.
  • Rack 2.0, or Rack for the Future
    • https://gist.github.com/raggi/11c3491561802e573a47
  • Listet die Vor- und Nachteile von Rack auf.
  • the_metal: A spike for thoughts about Rack 2.0
    • https://github.com/tenderlove/the_metal
  • Ein Experiment des Nokogiri-Autors Tenderlove zur Bestimmung der Mindestspezifikationen für Anforderungs- und Antwortobjekte. Es gibt eine interessante Meinung zu Issue.
  • halorgium/rack2: async hax for rack
    • https://github.com/halorgium/rack2
  • Versuch, das Rack asynchron zu machen?

Recommended Posts