Writing Clojure Webapps with Ring – 用Ring编写Clojure网络应用程序

最后修改: 2019年 5月 4日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Ring is a library for writing web applications in Clojure. It supports everything needed to write fully-featured web apps and has a thriving ecosystem to make it even more powerful.

Ring是一个用于用Clojure编写Web应用程序的库。它支持编写功能齐全的网络应用程序所需的一切,并拥有一个蓬勃发展的生态系统,使其更加强大。

In this tutorial, we’ll give an introduction to Ring, and show some of the things that can we can achieve with it.

在本教程中,我们将对Ring进行介绍,并展示一些我们可以用它实现的东西。

Ring isn’t a framework designed for creating REST APIs, like so many modern toolkits. It’s a lower-level framework to handle HTTP requests in general, with a focus on traditional web development. However, some libraries build on top of it to support many other desired application structures.

Ring并不是一个为创建REST API而设计的框架,就像许多现代工具包一样。它是一个低级别的框架,用于处理一般的HTTP请求,重点是传统的Web开发。然而,一些库建立在它的基础上,以支持许多其他想要的应用结构。

2. Dependencies

2.依赖性

Before we can start working with Ring, we need to add it to our project. The minimum dependencies we need are:

在我们开始使用Ring之前,我们需要将它添加到我们的项目中。我们需要的最低依赖性是

We can add these to our Leiningen project:

我们可以把这些加入到我们的Leiningen项目中。

  :dependencies [[org.clojure/clojure "1.10.0"]
                 [ring/ring-core "1.7.1"]
                 [ring/ring-jetty-adapter "1.7.1"]]

We can then add this to a minimal project:

然后我们可以将其添加到一个最小的项目中。

(ns ring.core
  (:use ring.adapter.jetty))

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body "Hello World"})

(defn -main
  [& args]
  (run-jetty handler {:port 3000}))

Here, we’ve defined a handler function – which we’ll cover soon – which always returns the string “Hello World”. Also, we’ve added our main function to use this handler – it’ll listen for requests on port 3000.

在这里,我们定义了一个处理函数–我们很快就会介绍–它总是返回字符串 “Hello World”。此外,我们还添加了我们的主函数来使用这个处理程序–它将监听3000端口的请求。

3. Core Concepts

3.核心概念

Leiningen has a few core concepts around which everything builds: Requests, Responses, Handlers, and Middleware.

Leiningen有几个核心概念,一切都围绕着这些概念展开。请求、响应、处理程序和中间件。

3.1. Requests

3.1.要求

Requests are a representation of incoming HTTP requests. Ring represents a request as a map, allowing our Clojure application to interact with the individual fields easily. There’re a standard set of keys in this map, including but not limited to:

请求是对传入的HTTP请求的一种表示。Ring将一个请求表示为一个地图,使我们的Clojure应用程序能够轻松地与各个字段进行交互。这个映射中有一组标准的键,包括但不限于:。

  • :uri – The full URI path.
  • :query-string – The full query string.
  • :request-method – The request method, one of :get, :head, :post, :put, :delete or :options.
  • :headers – A map of all the HTTP headers provided to the request.
  • :body – An InputStream representing the request body, if present.

Middleware may add more keys to this map as well as needed.

中间件也可以根据需要向该映射添加更多的键

3.2. Responses

3.2.应对措施

Similarly, responses are a representation of the outgoing HTTP responses. Ring also represents these as maps with three standard keys:

同样地,响应是对发出的HTTP响应的表示。Ring也将这些表示为具有三个标准键的映射

  • :status – The status code to send back
  • :headers – A map of all the HTTP headers to send back
  • :body – The optional body to send back

As before, Middleware may alter this between our handler producing it and the final result getting sent to the client.

和以前一样,中间件可能会在我们的处理程序产生和最终结果被发送到客户端之间改变它

Ring also provides some helpers to make building the responses easier.

Ring还提供了一些帮助工具,以使构建响应更容易

The most basic of these is the ring.util.response/response function, which creates a simple response with a status code of 200 OK:

其中最基本的是ring.util.response函数,它创建了一个状态代码为200 OK的简单响应。

ring.core=> (ring.util.response/response "Hello")
{:status 200, :headers {}, :body "Hello"}

There are a few other methods that go along with this for common status codes – for example, bad-request, not-found and redirect:

对于常见的状态代码,还有一些其他的方法与此相配合 – 例如,bad-requestnot-foundredirect

ring.core=> (ring.util.response/bad-request "Hello")
{:status 400, :headers {}, :body "Hello"}
ring.core=> (ring.util.response/created "/post/123")
{:status 201, :headers {"Location" "/post/123"}, :body nil}
ring.core=> (ring.util.response/redirect "https://ring-clojure.github.io/ring/")
{:status 302, :headers {"Location" "https://ring-clojure.github.io/ring/"}, :body ""}

We also have the status method that will convert an existing response to any arbitrary status code:

我们还有一个status 方法,它将把一个现有的响应转换为任意的状态代码。

ring.core=> (ring.util.response/status (ring.util.response/response "Hello") 409)
{:status 409, :headers {}, :body "Hello"}

We then have some methods to adjust other features of the response similarly – for example, content-type, header or set-cookie:

然后我们有一些方法可以类似地调整响应的其他特征–例如,content-type,headerset-cookie

ring.core=> (ring.util.response/content-type (ring.util.response/response "Hello") "text/plain")
{:status 200, :headers {"Content-Type" "text/plain"}, :body "Hello"}
ring.core=> (ring.util.response/header (ring.util.response/response "Hello") "X-Tutorial-For" "Baeldung")
{:status 200, :headers {"X-Tutorial-For" "Baeldung"}, :body "Hello"}
ring.core=> (ring.util.response/set-cookie (ring.util.response/response "Hello") "User" "123")
{:status 200, :headers {}, :body "Hello", :cookies {"User" {:value "123"}}}

Note that the set-cookie method adds a whole new entry to the response map. This needs the wrap-cookies middleware to process it correctly for it to work.

请注意,set-cookie方法为响应地图添加了一个全新的条目这需要wrap-cookies中间件正确处理它,才能发挥作用。

3.3. Handlers

3.3 处理者

Now that we understand requests and responses, we can start to write our handler function to tie it together.

现在我们理解了请求和响应,我们可以开始编写我们的处理函数,将其联系起来。

A handler is a simple function that takes the incoming request as a parameter and returns the outgoing response. What we do in this function is entirely up to our application, as long as it fits this contract.

处理程序是一个简单的函数,它将传入的请求作为参数并返回传出的响应。我们在这个函数中做什么完全取决于我们的应用程序,只要它符合这个契约。

At the very simplest, we could write a function that always returns the same response:

最简单的是,我们可以写一个总是返回相同响应的函数。

(defn handler [request] (ring.util.response/response "Hello"))

We can interact with the request as needed as well.

我们也可以根据需要与请求进行互动。

For example, we could write a handler to return the incoming IP Address:

例如,我们可以写一个处理程序来返回传入的IP地址。

(defn check-ip-handler [request]
    (ring.util.response/content-type
        (ring.util.response/response (:remote-addr request))
        "text/plain"))

3.4. Middleware

3.4.中间件

Middleware is a name that’s common in some languages but less so in the Java world. Conceptually they are similar to Servlet Filters and Spring Interceptors.

中间件这个名字在某些语言中很常见,但在Java世界中却不那么常见。在概念上,它们与Servlet过滤器和Spring拦截器类似。

In Ring, middleware refers to simple functions that wrap the main handler and adjusts some aspects of it in some way. This could mean mutating the incoming request before it’s processed, mutating the outgoing response after it’s generated or potentially doing nothing more than logging how long it took to process.

在Ring中,中间件指的是包裹主处理程序并以某种方式调整它的某些方面的简单函数。这可能意味着在处理之前突变传入的请求,在生成之后突变传出的响应,或者可能只是记录处理所需的时间。

In general, middleware functions take a first parameter of the handler to wrap and returns a new handler function with the new functionality.

一般来说,中间件函数接收要包装的处理程序的第一个参数,并返回一个具有新功能的新处理程序函数

The middleware can use as many other parameters as needed. For example, we could use the following to set the Content-Type header on every response from the wrapped handler:

中间件可以根据需要使用许多其他参数。例如,我们可以用下面的方法来设置来自封装处理程序的每个响应的Content-Type头。

(defn wrap-content-type [handler content-type]
  (fn [request]
    (let [response (handler request)]
      (assoc-in response [:headers "Content-Type"] content-type))))

Reading through it we can see that we return a function that takes a request – this’s the new handler. This will then call the provided handler and then return a mutated version of the response.

通过阅读我们可以看到,我们返回一个接受请求的函数–这是新的处理程序。然后,这将调用所提供的处理程序,然后返回一个变异版本的响应。

We can use this to produce a new handler by simply chaining them together:

我们可以利用这一点,通过简单地将它们串联起来产生一个新的处理程序。

(def app-handler (wrap-content-type handler "text/html"))

Clojure also offers a way to chain many together in a more natural way – by the use of Threading Macros. These are a way to provide a list of functions to call, each with the output of the previous one.

Clojure还提供了一种方法,可以以更自然的方式将许多函数链在一起–通过使用线程宏。这些是提供一个要调用的函数列表的方法,每个函数都有前一个函数的输出。

In particular, we want the Thread First macro, ->. This will allow us to call each middleware with the provided value as the first parameter:

尤其是,我们想要线程优先宏,->这将允许我们以提供的值作为第一个参数来调用每个中间件。

(def app-handler
  (-> handler
      (wrap-content-type "text/html")
      wrap-keyword-params
      wrap-params))

This has then produced a handler that’s the original handler wrapped in three different middleware functions.

这就产生了一个处理程序,它是由三个不同的中间件函数包裹的原始处理程序。

4. Writing Handlers

4.编写处理程序

Now that we understand the components that make up a Ring application, we need to know what we can do with the actual handlers. These are the heart of the entire application and is where the majority of the business logic will go.

现在我们已经了解了构成Ring应用程序的组件,我们需要知道我们可以对实际的处理程序做什么。这些是整个应用程序的核心,也是大部分业务逻辑的所在。

We can put whatever code we wish into these handlers, including database access or calling other services. Ring gives us some additional abilities for working directly with the incoming requests or outgoing responses that are very useful as well.

我们可以把任何我们希望的代码放到这些处理程序中,包括数据库访问或调用其他服务。Ring为我们提供了一些额外的能力,可以直接处理传入的请求或传出的响应,这也是非常有用的。

4.1. Serving Static Resources

4.1.服务静态资源

One of the simplest functions that any web application can perform is to serve up static resources. Ring provides two middleware functions to make this easy – wrap-file and wrap-resource.

任何Web应用程序可以执行的最简单的功能之一是提供静态资源。Ring提供了两个中间件函数,使之变得简单 – wrap-filewrap-resource

The wrap-file middleware takes a directory on the filesystem. If the incoming request matches a file in this directory then that file gets returned instead of calling the handler function:

wrap-file中间件需要一个文件系统上的目录。如果传入的请求与该目录中的一个文件相匹配,那么该文件将被返回,而不是调用处理函数。

(use 'ring.middleware.file)
(def app-handler (wrap-file your-handler "/var/www/public"))

In a very similar manner, the wrap-resource middleware takes a classpath prefix in which it looks for the files:

以一种非常类似的方式,wrap-resource中间件需要一个classpath前缀,它在其中寻找文件

(use 'ring.middleware.resource)
(def app-handler (wrap-resource your-handler "public"))

In both cases, the wrapped handler function is only ever called if a file isn’t found to return to the client.

在这两种情况下,只有在没有找到文件的情况下才会调用包裹的处理函数来返回给客户

Ring also provides additional middleware to make these cleaner to use over the HTTP API:

Ring还提供了额外的中间件,使这些在HTTP API上的使用更加简洁。

(use 'ring.middleware.resource
     'ring.middleware.content-type
     'ring.middleware.not-modified)

(def app-handler
  (-> your-handler
      (wrap-resource "public")
      wrap-content-type
      wrap-not-modified)

The wrap-content-type middleware will automatically determine the Content-Type header to set based on the filename extension requested. The wrap-not-modified middleware compares the If-Not-Modified header to the Last-Modified value to support HTTP caching, only returning the file if it’s needed.

wrap-content-type中间件将根据请求的文件名扩展名自动确定要设置的Content-Type头。wrap-not-modified中间件将If-Not-Modified头与Last-Modified值进行比较,以支持HTTP缓存,只在需要时返回文件。

4.2. Accessing Request Parameters

4.2.访问请求参数

When processing a request, there are some important ways that the client can provide information to the server. These include query string parameters – included in the URL and form parameters – submitted as the request payload for POST and PUT requests.

在处理一个请求时,有一些重要的方式,客户端可以向服务器提供信息。这些包括查询字符串参数–包括在URL和表单参数中–作为POST和PUT请求的有效载荷提交。

Before we can use parameters, we must use the wrap-params middleware to wrap the handler. This correctly parses the parameters, supporting URL encoding, and makes them available to the request. This can optionally specify the character encoding to use, defaulting to UTF-8 if not specified:

在我们使用参数之前,我们必须使用wrap-params中间件来包装处理程序。这将正确地解析参数,支持URL编码,并使它们对请求可用。这可以选择指定要使用的字符编码,如果没有指定,则默认为UTF-8。

(def app-handler
  (-> your-handler
      (wrap-params {:encoding "UTF-8"})
  ))

Once done, the request will get updated to make the parameters available. These go into appropriate keys in the incoming request:

一旦完成,请求将被更新以使参数可用。这些参数将进入传入请求的适当的键中。

  • :query-params – The parameters parsed out of the query string
  • :form-params – The parameters parsed out of the form body
  • :params – The combination of both :query-params and :form-params

We can make use of this in our request handler exactly as expected.

我们可以完全按照预期在我们的请求处理程序中使用它。

(defn echo-handler [{params :params}]
    (ring.util.response/content-type
        (ring.util.response/response (get params "input"))
        "text/plain"))

This handler will return a response containing the value from the parameter input.

这个处理程序将返回一个包含参数input值的响应。

Parameters map to a single string if only one value is present, or to a list if multiple values are present.

如果只有一个值,参数映射为一个字符串,如果有多个值,则映射为一个列表

For example, we get the following parameter maps:

例如,我们得到以下参数图。

// /echo?input=hello
{"input "hello"}

// /echo?input=hello&name=Fred
{"input "hello" "name" "Fred"}

// /echo?input=hello&input=world
{"input ["hello" "world"]}

4.3. Receiving File Uploads

4.3.接收文件上传

Often we want to be able to write web applications that users can upload files to. In the HTTP protocol, this is typically handled using Multipart requests. These allow for a single request to contain both form parameters and a set of files.

通常,我们希望能够编写用户可以上传文件的网络应用程序。在HTTP协议中,这通常是使用多部分请求来处理的。这些请求允许一个单一的请求同时包含表单参数和一组文件。

Ring comes with a middleware called wrap-multipart-params to handle this kind of request. This is similar to the way that wrap-params parses simple requests.

Ring附带了一个名为wrap-multipart-params的中间件来处理这种请求。这与wrap-params解析简单请求的方式类似。

wrap-multipart-params automatically decodes and stores any uploaded files onto the file system and tells the handler where they are for it to work with them:

wrap-multipart-params自动解码并将任何上传的文件存储到文件系统中,并告诉处理程序它们的位置,以便它能处理它们。

(def app-handler
  (-> your-handler
      wrap-params
      wrap-multipart-params
  ))

By default, the uploaded files get stored in the temporary system directory and automatically deleted after an hour. Note that this does require that the JVM is still running for the next hour to perform the cleanup.

默认情况下,上传的文件被存储在系统的临时目录中,并在一小时后自动删除。请注意,这确实需要JVM在下一小时内仍在运行,以执行清理工作。

If preferred, there’s also an in-memory store, though obviously, this risks running out of memory if large files get uploaded.

如果愿意的话,还有一个内存存储,不过很明显,如果上传大文件,这有可能会耗尽内存。

We can also write our storage engines if needed, as long as it fulfills the API requirements.

如果需要,我们也可以编写我们的存储引擎,只要它满足API的要求。

(def app-handler
  (-> your-handler
      wrap-params
      (wrap-multipart-params {:store ring.middleware.multipart-params.byte-array/byte-array-store})
  ))

Once this middleware is set up, the uploaded files are available on the incoming request object under the params key. This is the same as using the wrap-params middleware. This entry is a map containing the details needed to work with the file, depending on the store used.

一旦这个中间件被设置好,上传的文件就可以在params键下的传入请求对象中使用。这与使用wrap-params中间件的情况相同。这个条目是一个地图,包含了处理文件所需的细节,这取决于所使用的商店。

For example, the default temporary file store returns values:

例如,默认的临时文件库返回值。

  {"file" {:filename     "words.txt"
           :content-type "text/plain"
           :tempfile     #object[java.io.File ...]
           :size         51}}

Where the :tempfile entry is a java.io.File object that directly represents the file on the file system.

其中:tempfile条目是一个java.io.File对象,直接代表文件系统中的文件。

4.4. Working With Cookies

4.4.利用Cookies工作

Cookies are a mechanism where the server can provide a small amount of data that the client will continue to send back on subsequent requests. This is typically used for session IDs, access tokens, or persistent user data such as the configured localization settings.

Cookies是一种机制,服务器可以提供少量的数据,客户端将在随后的请求中继续发送回来。这通常用于会话ID、访问令牌或持久的用户数据,如配置的本地化设置。

Ring has middleware that will allow us to work with cookies easily. This will automatically parse cookies on incoming requests, and will also allow us to create new cookies on outgoing responses.

Ring有中间件,将使我们能够轻松地与cookie合作这将自动地解析传入请求的cookies。并将允许我们在发出的响应上创建新的cookie

Configuring this middleware follows the same patterns as before:

配置这个中间件的模式与之前一样。

(def app-handler
  (-> your-handler
      wrap-cookies
  ))

At this point, all incoming requests will have their cookies parsed and put into the :cookies key in the request. This will contain a map of the cookie name and value:

在这一点上,所有传入的请求将被解析并放入请求中的:cookies键中。这将包含一个cookie名称和值的映射。

{"session_id" {:value "session-id-hash"}}

We can then add cookies to outgoing responses by adding the :cookies key to the outgoing response. We can do this by creating the response directly:

然后我们可以通过将:cookies键添加到传出响应中。我们可以通过直接创建响应来做到这一点。

{:status 200
 :headers {}
 :cookies {"session_id" {:value "session-id-hash"}}
 :body "Setting a cookie."}

There’s also a helper function that we can use to add cookies to responses, in a similar way to how earlier we could set status codes or headers:

还有一个辅助函数,我们可以用它来给响应添加cookie,其方式类似于早期我们可以设置状态代码或头文件。

(ring.util.response/set-cookie 
    (ring.util.response/response "Setting a cookie.") 
    "session_id" 
    "session-id-hash")

Cookies can also have additional options set on them, as needed for the HTTP specification. If we’re using set-cookie then we provide these as a map parameter after the key and value. The keys to this map are:

Cookie还可以根据HTTP规范的需要,对其进行额外的选项设置。如果我们使用set-cookie,那么我们就在key和value之后提供这些作为map参数。这个映射的键是。

  • :domain – The domain to restrict the cookie to
  • :path – The path to restrict the cookie to
  • :secure – true to only send the cookie on HTTPS connections
  • :http-onlytrue to make the cookie inaccessible to JavaScript
  • :max-age – The number of seconds after which the browser deletes the cookie
  • :expires – A specific timestamp after which the browser deletes the cookie
  • :same-site – If set to :strict, then the browser won’t send this cookie back with cross-site requests.
(ring.util.response/set-cookie
    (ring.util.response/response "Setting a cookie.")
    "session_id"
    "session-id-hash"
    {:secure true :http-only true :max-age 3600})

4.5. Sessions

4.5.届会

Cookies give us the ability to store bits of information that the client sends back to the server on every request. A more powerful way of achieving this is to use sessions. These get stored entirely on the server, but the client maintains the identifier that determines which session to use.

Cookies使我们有能力存储客户在每次请求时发回给服务器的信息位。实现这一目标的一个更强大的方法是使用会话。这些信息完全存储在服务器上,但客户端保持着决定使用哪个会话的标识符。

As with everything else here, sessions are implemented using a middleware function:

与这里的其他东西一样,会话是使用中间件函数实现的

(def app-handler
  (-> your-handler
      wrap-session
  ))

By default, this stores session data in memory. We can change this if needed, and Ring comes with an alternative store that uses cookies to store all of the session data.

默认情况下,这将会话数据存储在内存中。如果需要,我们可以改变这一点,Ring附带了一个替代存储,使用cookies来存储所有会话数据。

As with uploading files, we can provide our storage function if needed.

与上传文件一样,如果需要,我们可以提供我们的存储功能

(def app-handler
  (-> your-handler
      wrap-cookies
      (wrap-session {:store (cookie-store {:key "a 16-byte secret"})})
  ))

We can also adjust the details of the cookie used to store the session key.

我们还可以调整用于存储会话密钥的cookie的细节

For example, to make it so that the session cookie persists for one hour we could do:

例如,为了使会话cookie持续一个小时,我们可以这样做。

(def app-handler
  (-> your-handler
      wrap-cookies
      (wrap-session {:cookie-attrs {:max-age 3600}})
  ))

The cookie attributes here are the same as supported by the wrap-cookies middleware.

这里的cookie属性与wrap-cookies中间件所支持的相同。

Sessions can often act as data stores to work with. This doesn’t always work as well in a functional programming model, so Ring implements them slightly differently.

会话通常可以作为数据存储来工作。这在函数式编程模型中并不总是那么好用,所以Ring对它们的实现略有不同。

Instead, we access the session data from the request, and we return a map of data to store into it as part of the response. This is the entire session state to store, not only the changed values.

相反,我们从请求中访问会话数据,并且我们返回一个数据映射,作为响应的一部分存储到其中。这是要存储的整个会话状态,而不仅仅是变化的值。

For example, the following keeps a running count of how many times the handler has been requested:

例如,下面记录了处理程序被请求了多少次的运行计数。

(defn handler [{session :session}]
  (let [count   (:count session 0)
        session (assoc session :count (inc count))]
    (-> (response (str "You accessed this page " count " times."))
        (assoc :session session))))

Working this way, we can remove data from the session simply by not including the key. We can also delete the entire session by returning nil for the new map.

通过这种方式,我们可以从会话中删除数据,只需不包括键。我们还可以通过返回nil来删除整个会话的新地图。

(defn handler [request]
  (-> (response "Session deleted.")
      (assoc :session nil)))

5. Leiningen Plugin

5.Leiningen插件

Ring provides a plugin for the Leiningen build tool to aid both development and production.

Ring为Leiningen构建工具提供了一个插件,以帮助开发和生产。

We set up the plugin by adding the correct plugin details to the project.clj file:

我们通过在project.clj文件中添加正确的插件细节来设置该插件。

  :plugins [[lein-ring "0.12.5"]]
  :ring {:handler ring.core/handler}

It’s important that the version of lein-ring is correct for the version of Ring. Here we’ve been using Ring 1.7.1, which means we need lein-ring 0.12.5. In general, it’s safest to just use the latest version of both, as seen on Maven central or with the lein search command:

重要的是,lein-ring的版本对Ring的版本是正确的。这里我们一直在使用Ring 1.7.1,这意味着我们需要lein-ring0.12.5。一般来说,使用两者的最新版本是最安全的,可以在Maven中心或通过lein search命令看到。

$ lein search ring-core
Searching clojars ...
[ring/ring-core "1.7.1"]
  Ring core libraries.

$ lein search lein-ring
Searching clojars ...
[lein-ring "0.12.5"]
  Leiningen Ring plugin

The :handler parameter to the :ring call is the fully-qualified name of the handler that we want to use. This can include any middleware that we’ve defined.

:handler调用的:ring参数是我们想要使用的handler的全称。这可以包括我们定义的任何中间件。

Using this plugin means that we no longer need a main function. We can use Leiningen to run in development mode, or else we can build a production artifact for deployment purposes. Our code now comes down exactly to our logic and nothing more.

使用这个插件意味着我们不再需要一个主函数。我们可以使用Leiningen在开发模式下运行,否则我们可以为部署目的建立一个生产工件。我们的代码现在完全归结为我们的逻辑,没有别的了

5.1. Building a Production Artifact

5.1.构建一个生产工件

Once this is set up, we can now build a WAR file that we can deploy to any standard servlet container:

一旦设置完毕,我们现在可以建立一个WAR文件,我们可以将其部署到任何标准的servlet容器上

$ lein ring uberwar
2019-04-12 07:10:08.033:INFO::main: Logging initialized @1054ms to org.eclipse.jetty.util.log.StdErrLog
Created ./clojure/ring/target/uberjar/ring-0.1.0-SNAPSHOT-standalone.war

We can also build a standalone JAR file that will run our handler exactly as expected:

我们也可以建立一个独立的JAR文件,它将完全按照预期运行我们的处理器

$ lein ring uberjar
Compiling ring.core
2019-04-12 07:11:27.669:INFO::main: Logging initialized @3016ms to org.eclipse.jetty.util.log.StdErrLog
Created ./clojure/ring/target/uberjar/ring-0.1.0-SNAPSHOT.jar
Created ./clojure/ring/target/uberjar/ring-0.1.0-SNAPSHOT-standalone.jar

This JAR file will include a main class that will start the handler in the embedded container that we included. This will also honor an environment variable of PORT allowing us to easily run it in a production environment:

这个JAR文件将包括一个主类,它将在我们包含的嵌入式容器中启动处理器。这也将尊重PORT的环境变量,使我们能够在生产环境中轻松地运行它。

PORT=2000 java -jar ./clojure/ring/target/uberjar/ring-0.1.0-SNAPSHOT-standalone.jar
2019-04-12 07:14:08.954:INFO::main: Logging initialized @1009ms to org.eclipse.jetty.util.log.StdErrLog
WARNING: seqable? already refers to: #'clojure.core/seqable? in namespace: clojure.core.incubator, being replaced by: #'clojure.core.incubator/seqable?
2019-04-12 07:14:10.795:INFO:oejs.Server:main: jetty-9.4.z-SNAPSHOT; built: 2018-08-30T13:59:14.071Z; git: 27208684755d94a92186989f695db2d7b21ebc51; jvm 1.8.0_77-b03
2019-04-12 07:14:10.863:INFO:oejs.AbstractConnector:main: Started ServerConnector@44a6a68e{HTTP/1.1,[http/1.1]}{0.0.0.0:2000}
2019-04-12 07:14:10.863:INFO:oejs.Server:main: Started @2918ms
Started server on port 2000

5.2. Running in Development Mode

5.2.在开发模式下运行

For development purposes, we can run the handler directly from Leiningen without needing to build and run it manually. This makes things easier for testing our application in a real browser:

为了开发的目的,我们可以直接从Leiningen运行处理程序,而不需要手动构建和运行它。这使得在真实的浏览器中测试我们的应用程序更加容易。

$ lein ring server
2019-04-12 07:16:28.908:INFO::main: Logging initialized @1403ms to org.eclipse.jetty.util.log.StdErrLog
2019-04-12 07:16:29.026:INFO:oejs.Server:main: jetty-9.4.12.v20180830; built: 2018-08-30T13:59:14.071Z; git: 27208684755d94a92186989f695db2d7b21ebc51; jvm 1.8.0_77-b03
2019-04-12 07:16:29.092:INFO:oejs.AbstractConnector:main: Started ServerConnector@69886d75{HTTP/1.1,[http/1.1]}{0.0.0.0:3000}
2019-04-12 07:16:29.092:INFO:oejs.Server:main: Started @1587ms

This also honors the PORT environment variable if we’ve set that.

如果我们设置了PORT环境变量,这也是对该变量的尊重。

Additionally, there’s a Ring Development library that we can add to our project. If this is available, then the development server will attempt to automatically reload any detected source changes. This can give us an efficient workflow of changing the code and seeing it live in our browser. This requires the ring-devel dependency adding:

此外,有一个环形开发库,我们可以添加到我们的项目中。如果这是可用的,那么开发服务器将尝试自动重新加载任何检测到的源代码变化。这可以给我们一个高效的工作流程,改变代码并在我们的浏览器中看到它的存在。这需要添加ring-devel依赖性。

[ring/ring-devel "1.7.1"]

6. Conclusion

6.结论

In this article, we gave a brief introduction to the Ring library as a means to write web applications in Clojure. Why not try it on the next project?

在这篇文章中,我们简要介绍了Ring库作为用Clojure编写Web应用程序的一种手段。为什么不在下一个项目中试试呢?

Examples of some of the concepts we’ve covered here can be seen in GitHub.

GitHub中可以看到我们在这里涉及的一些概念的例子。