zhxy/http服务器/appweb-4.3.4-0/doc/guide/esp/users/controllers.html

503 lines
30 KiB
HTML

<!-- BeginDsi "dsi/head.html" -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Embedthis Appweb 4.3.4 Documentation</title>
<meta name="keywords" content="embedded web server, web server software, embedded HTTP, application web server,
embedded server, small web server, HTTP server, library web server, library HTTP, HTTP library" />
<meta name="description" content="Embedthis Sofware provides commercial and open source embedded web servers for
devices and applications." />
<meta name="robots" content="index,follow" />
<link href="../../../doc.css" rel="stylesheet" type="text/css" />
<link href="../../../print.css" rel="stylesheet" type="text/css" media="print"/>
<!--[if IE]>
<link href="../../../iehacks.css" rel="stylesheet" type="text/css" />
<![endif]-->
<link href="http://www.google.com/cse/style/look/default.css" type="text/css" rel="stylesheet" />
</head>
<body>
<div class="top">
<a class="logo" href="http://appwebserver.org/">&nbsp;</a>
<div class="topRight">
<div class="search">
<div id="cse-search-form"></div>
<div class="version">Embedthis Appweb 4.3.4</div>
</div>
</div>
<div class="crumbs">
<a href="../../../index.html">Home</a>
<!-- EndDsi -->
&gt;<a href="index.html">ESP Guide</a>&gt; <b>Controllers and Actions</b>
</div>
</div>
<div class="content">
<div class="contentRight">
<h1>Quick Nav</h1>
<ul class="nav">
<li><a href="#example">Controller Example</a></li>
<li><a href="#routing">Routing Requests</a></li>
<li><a href="#config">Configuring a Controller</a></li>
<li><a href="#actions">Actions</a></li>
<li><a href="#context">Controller Context</a></li>
<li><a href="#parameters">Request Parameters</a></li>
<li><a href="#generating">Generating Controllers</a></li>
</ul>
<!-- BeginDsi "dsi/espSeeAlso.html" -->
<h1>See Also</h1>
<ul class="nav">
<li><a href="../../../guide/esp/users/using.html">ESP Overview</a></li>
<li><a href="../../../guide/esp/users/tour.html">ESP Tour</a></li>
<li><a href="../../../guide/esp/users/template.html">Templates and Layouts</a></li>
<li><a href="../../../guide/esp/users/controls.html">HTML Controls</a></li>
<li><a href="../../../guide/esp/users/config.html">ESP Configuration Directives</a></li>
<li><a href="../../../guide/esp/users/mvc.html">Model-View-Controller</a></li>
<li><a href="../../../guide/esp/users/generator.html">Application Generator</a></li>
<li><a href="../../../guide/esp/users/controllers.html">Controllers and Actions</a></li>
<li><a href="../../../guide/esp/users/database.html">Database Interface</a></li>
<li><a href="../../../guide/appweb/users/caching.html">Caching Responses</a></li>
</ul>
<!-- EndDsi -->
<ul>
<li><a href="../../../api/esp.html">ESP Library</a></li>
</ul>
</div>
<div class="contentLeft">
<h1>Controllers and Actions</h1>
<p>The ESP Model-view-controller (MVC) architecture is a pattern for designing well-structured applications and
MVC applications have proven to be an effective organization for web applications. A key reason is that MVC apps
separate the logic (<em>controller</em>), from the presentation (<em>view</em>) and the application's
data (<em>model</em>). This clear partition of responsibilities, scales well and helps to create maintainable
applications.</p>
<p>MVC <em>controllers</em> are the conductors of the application and they orchestrate the application's
activities. Via <em>action</em> functions, they receive client requests and generate appropriate responses,
mutating the applications data model in the process.</p>
<a id="example"></a>
<h2 class="section">Example of a Controller</h2>
<p>So what does a controller look like? Here is a partial example that has four actions:</p>
<pre class="light">
#include "esp.h"
static void create() {
/* Create post here */
redirect("list")
}
static void list() { }
}
static void edit() {
/* Create post here */
}
static void update() {
/* Update post here */
renderView("post-edit");
}
ESP_EXPORT int esp_controller_post(HttpRoute *route, MprModule *module)
{
Edi *edi;
espDefineAction(route, "post-create", create);
espDefineAction(route, "post-edit", edit);
espDefineAction(route, "post-list", list);
espDefineAction(route, "post-update", update);
return 0;
}
</pre>
<p>Say the client issues a request for:</p>
<pre>
http://embedthis.com/app/post/create
</pre>
<p>assuming a correctly configured request route, the <em>create</em> action, in the <em>post</em>
controller will be run. This will create a new blog post and then render the <em>post-list.esp</em>
view of posts back to the client.</p>
<a id="routing"></a>
<h2 class="section">Routing Requests</h2>
<p>At the heart of understanding how controllers are loaded and actions are run, is understanding Appweb
routing. When Appweb receives a client request, it examines each of the request routes configured in the
<em>appweb.conf</em> configuration file, until it finds a matching route for the request URI. The selected
route will then break the URI into tokens and save the token values as request parameters.</p>
<p>For example: consider the URI format:</p>
<pre>
http://embedthis.com/APP/CONTROLLER/ACTION/ID
</pre>
<p>In this example, <em>APP</em> is the (optional) name of the application, <em>CONTROLLER</em> is the controller
name, <em>ACTION</em> is the name of the action method to run and <em>ID</em> is a selector for an element
in the Model. When the request URI is tokenized, the Appweb router will extract the controller name and then
use this name to load the appropriate controller to service the request.</p>
<h3>RESTful Routes</h3>
<p>A RESTful approach to designing URI routes is also often used where different controller
actions are linked to specific routes. The benefit being being simple, readable, RESTful URIs.</p>
<table title="routes">
<thead>
<tr><th>Name</th><th>Method</th><th>Pattern</th><th>Action</th></tr>
</thead>
<tbody>
<tr><td>init</td><td>GET</td><td>/CONTROLLER/init$</td><td>init</td></tr>
<tr><td>create</td><td>POST</td><td>/CONTROLLER(/)*$</td><td>create</td></tr>
<tr><td>edit</td><td>GET</td><td>/CONTROLLER/edit$</td><td>edit</td></tr>
<tr><td>show</td><td>GET</td><td>/CONTROLLER$</td><td>show</td></tr>
<tr><td>update</td><td>PUT</td><td>/CONTROLLER$</td><td>update</td></tr>
<tr><td>destroy</td><td>DELETE</td><td>/CONTROLLER$</td><td>destroy</td></tr>
<tr><td>default</td><td>*</td><td>/CONTROLLER/{action}$</td><td>cmd-${action}</td></tr>
</tbody>
</table>
<p>In either case, RESTFul or not, the request URI is broken into tokens that specify the controller and
action to invoke to service the request. When a request is received, ESP will create an instance of the
controller and invoke the requested action method.</p>
<a id="flow"></a>
<h3>Processing Flow</h3>
<p>Appweb processes requests in stages:</p>
<ol>
<li>Decode the URI and web request</li>
<li>Route an incoming request to the request handler (ESP)</li>
<li>If the request is for a web page, run that and render a response</li>
<li>Otherwise if the request is routed to a controller, load the controller</li>
<li>Run the specified controller action determined by the request route pattern</li>
<li>Optionally render a response view to the client</li>
</ol>
<a id="config"></a>
<h2 class="section">Configuring Controllers</h2>
<p>Controllers may be configured two ways:</p>
<ul>
<li>As part of an MVC application</li>
<li>As a stand-alone controller function.</li>
</ul>
<h3>MVC Configuration</h3>
<p>MVC applications are typically created via the <a href="generator.html">esp</a> generator command.
The <em>esp</em> command is used to create a bare MVC application with a directory for controllers,
views and databases. It is also used generate an <em>appweb.conf</em> configuration file to use when
you run Appweb to host your application. By default, this configuration file will have an
<em>EspApp</em> directive configured for your application. This directive creates a top level route for the
MVC application and then a set of routes for each of the RESTful verbs: create, destroy, edit, list, show
and update.</p>
<pre>EspApp Prefix Dir RouteSet Database</pre>
<p>The
<a href="../../appweb/users/dir/esp.html#espApp">EspApp</a> directive creates routes that intercept request URIs that begin with the given prefix
and directs them toward the MVC application in the specified Directory. The RouteSet is typically
"<em>restful</em>". Lastly, an optional database may be pre-opened for the MVC application. For example:</p>
<pre>EspApp /store ./applications/store restful mdb://app/db/store.mdb</pre>
<h3>Stand-Alone Controllers</h3>
<p>Sometimes, you may wish to have a controller without a full MVC application. You can do this by using a
set of Appweb directives.</p>
<pre>
&lt;Route ^/app/{controller}(/)*$ &gt;
Name /app/*/list
AddHandler espHandler
EspDir controllers ./controllers
Source test.c
Target run $1-list
&lt;/Route&gt;
</pre>
<p>This set of directives will create a route that will invoke the controller <em>controllers/test.c</em>.
If the <em>/app/test/</em> URI is invoked, the <em>list</em> action function will be invoked.
The EspDir directive nominates the directory to contain the controllers. By default this is configured to be
{Documents}/controllers. The Target directive defines how the action function name to invoke.
Note: This usage is less typical than using the MVC pattern.</p>
<a id="actions"></a>
<h2 class="section">Actions</h2>
<p>Actions are where the controller does its work. In ESP, actions are simple "C" functions and thus
they need to be registered before use. This is done in the controller initialization function.
The initialization function should be named: "<em>esp_controller_NAME</em>", where NAME is the unique
name of the controller. The first time the controller is invoked, the controller
library will be loaded and the initialization function will be run. Typically, the initialization function
will then call
<a href="../../../api/esp.html#group___esp_route_1ga321abe89cc91246b2a44f40493988670">espDefineAction</a>
to bind action functions to route actions.</p>
<h3>Missing Actions</h3>
<p>When responding to a request, if the required action is not found, ESP will look for an action called
"<em>CONTROLLER-missing</em>". If that is not defined in the controller, it will look for a global
"missing" function. Otherwise it will respond with a HTTP 404 error indicating that the required
action could not be found.</p>
<a id="processing"></a>
<h3>Processing the Request</h3>
<p>The controller action can perform any processing it desires. There are no real restrictions except you don't
want to block for too long without giving the client some feedback. Because Appweb is multithreaded, you can
block. In that case, the server will continue to run and serve other requests. However, note that threads
are a limited resource. It may be better to use non-blocking techniques such as async processing.</p>
<h3>Async Processing</h3>
<p>An action may service a long-running request without blocking, by responding in pieces.
An action function may return without completing the request. Normally, ESP will automatically finalize
the request when the action returns. To prevent this, call
<a href="../../../api/esp.html#group___esp_abbrev_1ga9db9b8b7e2c750f6f942da0478866de2">
dontAutoFinalize</a> to tell ESP not to finalize the request and to keep the connection and
response open. At anytime and from any other code, you may then call
<a href="../../../api/esp.html#group___esp_abbrev_1ga32d626626eee0bc4ade146973f6abb1c">finalize</a>
to complete the request. To force output to the client, without finalizing, use
<a href="../../../api/esp.html#group___esp_abbrev_1ga32d626626eee0bc4ade146973f6abb1c">flush</a>.</p>
<p>For example:</p>
<pre>
static void second(HttpConn *conn) {
setConn(conn);
render("World\n");
finalize();
}
static void first() {
dontAutoFinalize();
render("Hello ");
flush();
<b>setTimeout(second, 5000, getConn());</b>
}
</pre>
<p>This example will print "Hello ", then wait five seconds and then print "World". Note that the request
is held open, but Appweb is not blocked in any thread. The call to <em>setTimeout</em> will arrange to have
the Appweb event loop invoke <em>second</em> after five seconds have elapsed. This pattern is a
highly efficient in its use of system resources and scales very well.</p>
<h2>Loading and Caching Controllers</h2>
<p>Before a controller can run, it must first be compiled and linked into a loadable library.
On Windows, this will be a DLL, on Unix, it will be a shared library with a ".so" extension. On VxWorks it
will be a loadable task module.</p>
<p>The compilation of controllers into libraries happens automatically if a compiler is installed on the
system and if the <em>EspUpdate</em> directive is enabled. If so, when a request is received, ESP will
compile and link the controller in to a library and save the result into the ESP <em>cache</em> directory
for future use. After servicing the first request for the controller, the controller code is retained in
memory and the controller will not be reloaded unless the source code is modified. If Appweb is rebooted,
the cached library will be reloaded without recompiling. This provides two levels of caching: in-memory and
on-disk as a shared library.</p>
<h3>Development and Production Modes</h3>
<p>When a request for a controller is received, ESP will test if the source code has been updated to
determine if the controller must be recompiled. If the source has been changed, Appweb will wait for all
requests that are using the already loaded controller, to gracefully complete. When no requests are using
the old controller version, Appweb will unload the controller, and ESP will recompile the updated controller
source and create a new shared library that will then be loaded and the request servicing resumed.</p>
<p>If Appweb was configured and built in debug mode, the default value for <em>EspUpdate</em> will be
<em>on</em>. If Appweb was built in release mode, the default value is <em>off</em>. In release mode is it
common practice to lock down the compiled controllers and not auto-recompile once deployed.</p>
<a id="context"></a>
<h2 class="section">Controller Context</h2>
<p>ESP establishes a request context for the controller before invoking the controller action. The top level
of the context is represented by the <a href="../../../api/http.html#group___http_conn">HttpConn</a>
connection object. From this, all other request information can be reached, including the:</p>
<ul>
<li>receive object &mdash; describes the client HTTP request.</li>
<li>transmit object &mdash; describes the client HTTP response.</li>
<li>host object &mdash; describes the web server hosting the application.</li>
<li>params &mdash; request query, form, and routing parameters.</li>
<li>session state object &mdash; session state information.</li>
<li>flash messages &mdash; informational messages to pass to the next action (and view).</li>
</ul>
<h3>ESP Short-Form API</h3>
<p>When ESP invokes a controller action or a ESP web page, ESP defines the current HttpConn connection
object in Thread-Local storage. ESP also defines a terse, short-form,
<a href="../../../api/esp.html#group___esp_abbrev">API</a> that uses the current connection object to
provide context for the API. When using this API, explicit access to the connection object should not
typically be required. The ESP short-form API should cover 95% of requirements for action processing.</p>
<h3>Explicit Connection Access</h3>
<p>If explicit access to the connection object is required, action functions may
define a connection argument as
which is passed into all actions.</p>
<pre>static void ACTION(HttpConn *conn) {
/* Use the conn reference here */
}</pre>
<p>Alternatively, the connection object can can be retrieved using the <a
href="../../../api/esp.html#group___esp_abbrev_1gabe448b3542b4d1391e80e74192a09cb3">
getConn</a> API.</p>
<h3>Navigating the Connection Object</h3>
<p>The <a href="../../../api/http.html#group___http_conn">HttpConn</a> object represents the current
TCP/IP connection. By using HTTP KeepAlive, the connection may be utilized for multiple requests. The fields
of the HttpConn object are public and can be accessed and
navigated.</p>
<table title="properties">
<thead>
<tr><th>HttpConn Property</th><th>Purpose</th></tr>
</thead>
<tbody>
<tr><td>rx</td><td>Reference to the HttpRx receive object</td></tr>
<tr><td>tx</td><td>Reference to the HttpTx transmit object</td></tr>
<tr><td>host</td><td>Reference to the HttpHost object</td></tr>
<tr><td>http</td><td>Reference to the Http object</td></tr>
<tr><td>endpoint</td><td>Reference to the HttpEndpoint transmit object</td></tr>
<tr><td>limits</td><td>Reference to the HttpLimits object</td></tr>
<tr><td>ip</td><td>Remote client IP address</td></tr>
<tr><td>port</td><td>Remote client port</td></tr>
</tbody>
</table>
<h3>Navigating the Receive Object</h3>
<p>The <a href="../../../api/http.html#group___http_rx">HttpRx</a> object represents the receive side of the
HTTP protocol. On the server, it holds state regarding the client HTTP request. The fields of the HttpRx
object are public and can be accessed and navigated.</p>
<table title="fields">
<thead>
<tr><th>HttpRx Property</th><th>Purpose</th></tr>
</thead>
<tbody>
<tr><td>method</td><td>HTTP request method</td></tr>
<tr><td>uri</td><td>Current URI (may be rewritten)</td></tr>
<tr><td>pathInfo</td><td>Path portion of the URI after the scriptName</td></tr>
<tr><td>scriptName</td><td>ScriptName portion of the URI</td></tr>
<tr><td>length</td><td>Content length</td></tr>
<tr><td>route</td><td>Reference to current HttpRoute object</td></tr>
<tr><td>params</td><td>Request params (query, form and route parameters)</td></tr>
<tr><td>files</td><td>Uploaded files</td></tr>
</tbody>
</table>
<h3>Navigating the Tx Object</h3>
<p>The <a href="../../../api/http.html#group___http_tx">HttpTx</a> object represents the transmit side of the
HTTP protocol. On the server, it holds state regarding the response to the client. The fields of the HttpTx
object are public and can be accessed and navigated.</p>
<table title="state">
<thead>
<tr><th>HttpTx Property</th><th>Purpose</th></tr>
</thead>
<tbody>
<tr><td>filename</td><td>Name of the real file being served</td></tr>
<tr><td>ext</td><td>Filename extension</td></tr>
<tr><td>handler</td><td>Request handler object</td></tr>
<tr><td>length</td><td>Response content length</td></tr>
<tr><td>status</td><td>Response HTTP status</td></tr>
<tr><td>headers</td><td>Response HTTP headers</td></tr>
</tbody>
</table>
<a id="models"></a>
<h3>Sample of ESP API</h3>
<p>Here are a few of the ESP APIs that may be used inside controller actions:</p>
<table title="viewClass">
<thead>
<tr>
<th class="nowrap">Method / Property</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1gadba1deb080e78b4517119a0294489b44">
addHeader</a></td> <td>Add a response HTTP header.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1gab7b4049b554576b57f8cc49efc9e3a95">
createSession</a>
</td>
<td>Enable session control.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1ga35677b9aa8d61543db5ea80377e823a6">
destroySession</a>
</td>
<td>Destroy a session.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1ga9db9b8b7e2c750f6f942da0478866de2">
dontAutoFinalize</a>
</td>
<td>Don't automatically finalize output when the action returns. Useful for async actions.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1ga03544bf56dde3a257391d07e1d6f6a3a">
error</a>
</td>
<td>Send an error flash message to the next web page.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1ga8e8c0dccb4ded8a2fecec11d389cf8c8">
inform</a>
</td>
<td>Send an informational flash message to the next web page.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1gad638c34898123293ebc120c1f9396a9c">
param</a>
</td>
<td>Get a request parameter value.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1ga79cf206805a9595395af14e7c35f289d">
redirect</a></td>
<td>Redirect the client to a new URI.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1gaf89154adc3cbf6d6a6a32c6b4457c593">
render</a></td>
<td>Render the text data back to the client.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1gaa1e37f244a0e0796df494dfb756472a8">
renderFile</a></td>
<td>Render a file's contents back to the client.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1gafe8d897ff436eabc6fc275f76222a5c3">
setContentType</a></td>
<td>Set the response content type.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#esp_8h_1a85b884db9ea1993efaa01dbe686f601c">
setCookie</a></td>
<td>Define a cookie to include in the response.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1gadb4f7bc3020ab9c481f1ebcaf1ed3f2a">
setSessionVar</a></td>
<td>Set a variable in session state storage.</td>
</tr>
<tr>
<td><a href="../../../api/esp.html#group___esp_abbrev_1ga56d17d8860dc351f63feaa55891cdf21">
uri</a></td>
<td>Make a URI from parameters.</td>
</tr>
</tbody>
</table>
<a id="parameters"></a>
<h2 class="section">Request Parameters</h2>
<p>ESP will collect request query, form data and route parameters into one
<em>params</em> object which is accessible to actions via the
<a href="../../../api/esp.html#group___esp_abbrev_1gad638c34898123293ebc120c1f9396a9c">param</a> API. Each query key/value pair and all
request form elements posted by the client will become a properties of the <em>params</em> object.
When routing the request, Appweb will tokenize the URI and create parameters for each positional token in
the URI. The Controller name and Action are defined as the parameters: <em>controller</em> and
<em>token</em>.</p>
<a id="views"></a>
<h3>Rendering Views</h3>
<p>After processing the request, the controller is responsible for rendering a response back to the client.
The controller can choose how to respond. It may explicitly create the response body by calling
<a href="../../../api/esp.html#group___esp_abbrev_1gaf89154adc3cbf6d6a6a32c6b4457c593">render</a> to generate HTML.
Alternatively, the action may call
<a href="../../../api/esp.html#group___esp_abbrev_1gaf0db430f850378bd83c514a0dda77fb9">renderView</a> to
response with a view web page.
If the action method does not explicitly generate any response, ESP will invoke a view with the same name as
the action method.</p>
<a id="generating"></a>
<h2 class="section">Generating Controllers and Actions</h2>
<p>If you are creating an MVC application, you may use the ESP application generator, called <em>esp</em> to
make it quick and easy to create controllers, actions and controller scaffolds. To generate a new
controller, run:</p>
<pre>
esp generate CONTROLLER ACTIONS...
</pre>
<p><em>CONTROLLER</em> is the controller name. <em>ACTIONS...</em> are the names of the actions you want to
generate. This command will create the controller source file under the <em>controllers</em> directory. The
controller source will contain empty functions for each of the requested actions.
You can edit the controller source to meet your needs.</p>
</div>
</div>
<!-- BeginDsi "dsi/bottom.html" -->
<div class="bottom">
<p class="footnote">
<a href="../../../product/copyright.html" >&copy; Embedthis Software LLC, 2003-2013.
All rights reserved. Embedthis, Appweb, ESP, Ejscript and Embedthis GoAhead are trademarks of Embedthis Software LLC.</a>
</p>
</div>
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">
google.load('search', '1', {language : 'en'});
google.setOnLoadCallback(function() {
var customSearchControl = new google.search.CustomSearchControl(
'000262706376373952077:1hs0lhenihk');
customSearchControl.setResultSetSize(google.search.Search.FILTERED_CSE_RESULTSET);
var options = new google.search.DrawOptions();
options.enableSearchboxOnly("http://appwebserver.org/search.html");
customSearchControl.draw('cse-search-form', options);
}, true);
</script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-179169-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</body>
</html>