热搜: 活动 交友 discuz
查看: 2956|回复: 0

The Life of a Request——Zope3发布网页的过程

发表于 2014-11-29 13:58:00 | 显示全部楼层 |阅读模式

 You should be well{familiar with the Zope 3 framework.
 Having a general idea of Internet server design is also very helpful.
When developing Zope 3 applications, the coder is commonly dealing with the request object to create views without thinking much about the details on how the request gets into the view and what happens with the response that is constructed in it. And this is ne, since it is often not necessary to know.

But sometimes one needs to write a custom server or change the behavior of the publisher. In these cases it is good to know the general design of the Zope servers and publishers. This chapter takes you on the journey through the life of a request using the browser (special HTTP) request as an example.
40.1 What is a Request
The term \request" appears often when talking about Zope 3. But what is a request?
In technically concrete situations we usually refer to objects that implement IRequest. These objects are responsible to embed protocol-speci c details and represent the protocol semantics to provide them for usage by presentation components.It is not enough to only think of request objects, though. For me, anything that the client sends to the server after connection negotiations is considered a request. So on the very lowest level, when the user agent (in this case the classic Web browser) sends the HTTP string GET /index.html HTTP/1.1 it could be considered a request (raw) as well.
40.2 Finding the Origin of the Request Now that we have an idea what the request is, let's take a more technical approach and nd out how connections are handled technically and how a request is born in the midst of much redirection and many confusing abstraction layers.
When a server starts up, it binds a socket to an address on the local machine
( bind(address)) and then starts to listen for connections by calling
listen(backlog). When an incoming connection is detected, the accept() method
is called, which returns a connection object and the address of the computer to which
the connection was made. All of this is part of the standard Python socket library
and is documented in the zope.server.interfaces.ISocket interface.
The server, which is mainly an IDispatcher implementation, has a simple interface
to handle the speci c events, by calling its corresponding handle heventi()
methods. A complete list of all events that are managed this way is given
in the IDisplatcherEventHandler interface. So when a connection comes in,
handle accept() is called, which is overridden in the zope.server.serverbase.
ServerBase class around line 130. This method tries to get the connection by calling
accept() (see previous paragraph). If the connection was successfully created,
it is used to create a ServerChannel, which is the next level of abstraction. Most of
the other dispatcher functionality is provided by the standard async.dispatcher,
which fully implements IDispatcher.
At this stage the channel is taking over, which is just another dispatcher. So
the channel starts to collect the incoming data (see received(data)) and sends it
right away to the request parser, which is an instance of a class speci ed as parser
class. Obviously, this class will be di erent for every server implementation. The
way the parser functions is not that important. All we have to know is that it
implements IStreamConsumer, which has a way of saying when it has completed
parsing a request. Once all of the data is received, the IServerChannel method
receivedCompleteRequest(req) is called, whose goal is to schedule the request to
be executed. But only one request of the channel can be running at a time. So if

the channel is busy, then we need to queue the request, until the running task is
Whenever the channel becomes available (see end task(close), the next request
from the queue is taken and converted to an ITask object. The task is then immediately
sent to the server for execution using IServer.addTask(task). There it
is added to a task dispatcher (see ITaskDispatcher), which schedules the task for
execution. But why all this redirection through a task and a task dispatcher? Until
now, all the code ran on a single thread. But in order to scale the servers better
and to support long-running requests without blocking the entire server, it is necessary
to be able to start several threads to handle the requests. It is now up to
the ITaskDispatcher implementation to decide how to spread the requests. Theoretically,
it could even consult other computers to execute a task. By default, we
use the zope.server.taskthreads.ThreadedTaskDispatcher though. Using its
setThreadCount(count) method, the Zope startup code is able specify the maximum
amount of threads running at a time.
Once, it is the task's turn to be serviced, the task dispatcher calls ITask.
service() which should nally execute the request. Speci cally, when the HTTPTask
is serviced, the method executeRequest(task) of the HTTPServer is called.
The zope.server.http.publisherhttpserver.PublisherHTTPServer, which is
the one used for Zope 3, creates a IHTTPRequest object from the task and publishes
the request with zope.publisher.publish(request). The server has an attribute
request factory in which the request class that is used to create the request
is stored.
So what did the system accomplish so far? We have taken an incoming connection,
read all the incoming data and parsed it, scheduled it for execution, and nally
created a request that was published with the Zope 3 publisher. Except for the last
step, there was nothing Zope-speci c about this code, so that all of this could be
replaced by any other Web server, like Twisted's.
40.3 The Request and the Publisher
With the birth of the request and its start to walk the path of a request's life by
entering the publisher using zope.publisher.publish.publish(), it also enters a
Zope-pure domain. In fact, Zope does not really care how a request was created, as
long as it implements IRequest. For example, when functional tests are executed,
they create the request purely from ctional data and pass it through the publisher
to analyze the response afterwards.
From a high-level point of view, the publisher's publish() method is responsible
of interpreting the request's information and cause the correct actions. It starts

out by traversing the given object path to an actual object, then call the object
and nally writing the result in the response. Everything else in this method is
about handling errors and exceptions as well as providing enough hooks for other
components to step in.
The publish() method is so central to the entire Zope 3 framework, that we will
now go through it very carefully trying to understand each step's purpose. Wherever
necessary, we will take a rest and examine side paths closer. It might be of advantage
to open the zope.publisher.publish module at this point, so that it is easier to
follow the text.
The work of the publish() method starts with a in nite while{loop. The rst
step inside the loop is to get the publication.
The publication provides the publisher with hooks to accomplish applicationspeci
c tasks, related to data storages, transactions and security. The default implementation
is DefaultPublication, which is located in zope.publisher.base
and can be used by software that do not make use of the entire Zope framework. For
Zope 3, however, there is a speci c Zope implementation in
Now, wrapped inside three try/except statements, we tell the request to look at
its data and process whatever needs to be processed. In the case of a browser request,
like the one we use as example, the processInputs() tries to parse all HTML form
data that might have been sent by the browser and convert it to Python objects.
The next step is to convert the request's object path to an object, a process known
as traversal. Besides calling all the event{hooks, the rst step of the traversal process
is to determine the application or, in other words, the object root. For a common
Zope 3 installation, the application is of course the root of the ZODB. Then we use
the request's traverse(object) method to get to the desired object. Let's have a
closer look at this method for the BrowserRequest.
First of all we notice that the BrowserRequest's traverse method does not do any
of the heavy lifting, but only covers a few browser-speci c corner cases, like picking
a default view and using the HTML form data (by inspecting form variable names
for the sux \:method") for possible additional traversal steps. It turns out that the
BaseRequest's traverse method does all the work. At the beginning of the method
there are several private attributes that are being pulled into the local namespace
and setup.
 publication: It is simply the publication object from before, which gives us
access to the application-speci c functionality.
 traversal stack: A simple stack (i.e. list) of names that must be traversed.
These names came from the parsed path of the URL. For example \/path/-

to/foo/bar/index.html" would be parsed to ['path','to','foo','bar','
 traversed names: This is a list of names that have been already successfully
traversed. The names are simply the entries coming from traversal stack.
 last obj traversed: This variable keeps track of the last object that was
found in the traversal process.
Now we just work through the traversal stack until it has been completely emptied.
The interesting call here is the publication.traverseName(request,object,
name) which tries to determine the next object using the name from the traversal
stack and the request. The traverseName() method can be very complex. The Zope
3 application version, found in
PublicationTraverse, must be able to handle namespaces (\++namespace++"),
views (\@@") and pluggable traverser lookups, so that objects can implement their
own traversers. To discuss the details of this method would be beyond the goal of
this chapter.
If everything goes well, and no exception was raised, meaning that the object
speci ed in the path was found, the traverse() method returns the found object
and we are back in the publisher's publish() function. The next step is to execute
the object.
Calling the object assumes that the object is callable in the rst place. Therefore,
the traversal process should always end in a view or a method on a view.
But since all common content objects have browser-speci c default views, we
are guaranteed that the object is callable. For other presentation types, similar
default options exist. Even though the object is formally executed by calling
publication.callObject(request,object), eventually mapply() is called, which
is de ned in the zope.publisher.publish module. mapply() does not just call
the object, but takes great care of determining the argument and nding values for
When an object is called, it can either write the result directly to the request's
response object or return a result object. In the latter case, the publish() method
adds the result to the body of the response. Here it is assumed that the result object
is a unicode string. For the Zope application the afterCall(request) execution
is of importance, since it commits the transaction to the ZODB. This process can
cause a failure, so it is very important that we do not return any data to the server
until the transaction is committed.
When all this has successfully nished, we call outputBody() on the response,
which sends the data out to the world going through the task, channel and eventually
through the socket to the connected machine. Note that the output(data) method,

which is called from outputBody, is responsible for converting the internally kept
unicode strings to valid ASCII using an encoding. If no encoding was speci ed,
\UTF-8" is used by default.
Once the response has sent out its data, the request is closed by calling close()
on itself, which releases all locks on resources. This will also nish the running task,
close the channel and eventually disconnect the socket. This marks the end of the
Let's now look at some of the possible failure scenarios. The most common failure
is a ZODB write con
ict, in which case we simply want to rollback the transaction and
retry the request again. But where does the Retry error come from, when the ZODB
raises a ConflictError? A quick look in the publication's handleException()
method reveals, that if a write con
ict error is detected, it is logged and afterwards
a Retry exception is raised, so that the next exception handler is used. Here we
simply reset the request and the response and allow the publishing process to start
all over again (remember, we have an everlasting while loop over all of this code).
In general, though, exceptions are handled by the handleException() method,
which logs the error and even allows them to be formatted in the appropriate output
format using a view. See the chapter on \Changing Standard Exception Views" for
details on how to de ne your own views on exceptions.
This concludes our journey through the life of a request. Sometimes I intentionally
ignored details to stay focused and not confuse you. If you are interested, you will
nd that the interfaces of the various involved components serve well as further
documentation, especially for the publisher.

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册


QQ|小黑屋|Archiver|手机版|Plone技术资料 ( 湘ICP备14006519号-1 )

GMT+8, 2019-11-21 06:37 , Processed in 0.047911 second(s), 19 queries , Gzip On.

Powered by Plone! X3.4

© 2001-2019

快速回复 返回顶部 返回列表