您好,登錄后才能下訂單哦!
本篇內容主要講解“怎么理解用Java實現的超輕量級RESTful Web服務”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么理解用Java實現的超輕量級RESTful Web服務”吧!
Web 服務,以這樣或那樣的形式,已經存在了近二十年。比如,XML-RPC 服務出現在 90 年代后期,緊接著是用 SOAP 分支編寫的服務。在 XML-RPC 和 SOAP 這兩個開拓者之后出現后不久,REST 架構風格的服務在大約 20 年前也出現了。REST 風格(以下簡稱 Restful)服務現在主導了流行的網站,比如 eBay、Facebook 和 Twitter。盡管分布式計算的 Web 服務有很多替代品(如 Web 套接字、微服務和遠程過程調用的新框架),但基于 Restful 的 Web 服務依然具有吸引力,原因如下:
Restful 服務建立在現有的基礎設施和協議上,特別是 Web 服務器和 HTTP/HTTPS 協議。一個擁有基于 HTML 的網站的組織可以很容易地為客戶添加 Web 服務,這些客戶對數據和底層功能更感興趣,而不是對 HTML 的表現形式感興趣。比如,亞馬遜就率先通過網站和 Web 服務(基于 SOAP 或 Restful)提供相同的信息和功能。
Restful 服務將 HTTP 當作 API,因此避免了復雜的軟件分層,這種分層是基于 SOAP 的 Web 服務的明顯特征。比如,Restful API 支持通過 HTTP 命令(POST-GET-PUT-DELETE)進行標準的 CRUD(增加-讀取-更新-刪除)操作;通過 HTTP 狀態碼可以知道請求是否成功或者為什么失敗。
Restful Web 服務可以根據需要變得簡單或復雜。Restful 是一種風格,實際上是一種非常靈活的風格,而不是一套關于如何設計和構造服務的規定。(伴隨而來的缺點是,可能很難確定哪些服務不能算作 Restful 服務。)
作為使用者或者客戶端,Restful Web 服務與語言和平臺無關。客戶端發送 HTTP(S) 請求,并以適合現代數據交換的格式(如 JSON)接收文本響應。
幾乎每一種通用編程語言都至少對 HTTP/HTTPS 有足夠的(通常是強大的)支持,這意味著 Web 服務的客戶端可以用這些語言來編寫。
基于 Restful 的“小說” web 服務包含三個程序員定義的類:
Novel
類代表一個小說,只有三個屬性:機器生成的 ID、作者和標題。屬性可以根據實際情況進行擴展,但我還是想讓這個例子看上去更簡單一些。
Novels
類包含了用于各種任務的工具類:將一個 Novel
或者它們的列表的純文本編碼轉換成 XML 或者 JSON;支持在小說集合上進行 CRUD 操作;以及從存儲在文件中的數據初始化集合。Novels
類在 Novel
實例和 servlet 之間起中介作用。
NovelsServlet
類是從 HttpServlet
中繼承的,HttpServlet
是一段健壯且靈活的代碼,自 90 年代末的早期企業級 Java 就已經存在了。對于客戶端的 CRUD 請求,servlet 可以當作 HTTP 的端點。 servlet 代碼主要用于處理客戶端的請求和生成相應的響應,而將復雜的細節留給 Novels
類中的工具類進行處理。
一些 Java 框架,比如 Jersey(JAX-RS)和 Restlet,就是為 Restful 服務設計的。盡管如此,HttpServlet
本身為完成這些服務提供了輕量、靈活、強大且充分測試過的 API。我會通過下面的“小說”例子來說明。
當然,部署“小說” Web 服務需要一個 Web 服務器。我的選擇是 Tomcat,但是如果該服務托管在 Jetty 或者甚至是 Java 應用服務器上,那么這個服務應該至少可以工作(著名的最后一句話!)。在我的網站上有總結了如何安裝 Tomcat 的 README 文件和代碼。還有一個附帶文檔的 Apache Ant 腳本,可以用來構建“小說”服務(或者任何其他服務或網站),并且將它部署在 Tomcat 或相同的服務。
Tomcat 可以從它的官網上下載。當你在本地安裝后,將 TOMCAT_HOME
設置為安裝目錄。有兩個子目錄值得關注:
TOMCAT_HOME/bin
目錄包含了類 Unix 系統(startup.sh
和 shutdown.sh
)和 Windows(startup.bat
和 shutdown.bat
) 的啟動和停止腳本。Tomcat 作為 Java 應用程序運行。Web 服務器的 servlet 容器叫做 Catalina。(在 Jetty 中,Web 服務器和容器的名字一樣。)當 Tomcat 啟動后,在瀏覽器中輸入 http://localhost:8080/
可以查看詳細文檔,包括示例。
TOMCAT_HOME/webapps
目錄是已部署的 Web 網站和服務的默認目錄。部署網站或 Web 服務的直接方法是復制以 .war
結尾的 JAR 文件(也就是 WAR 文件)到 TOMCAT_HOME/webapps
或它的子目錄下。然后 Tomcat 會將 WAR 文件解壓到它自己的目錄下。比如,Tomcat 會將 novels.war
文件解壓到一個叫做 novels
的子目錄下,并且保留 novels.war
文件。一個網站或 Web 服務可以通過刪除 WAR 文件進行移除,也可以用一個新版 WAR 文件來覆蓋已有文件進行更新。順便說一下,調試網站或服務的第一步就是檢查 Tomcat 已經正確解壓 WAR 文件;如果沒有的話,網站或服務就無法發布,因為代碼或配置中有致命錯誤。
因為 Tomcat 默認會監聽 8080 端口上的 HTTP 請求,所以本機上的 URL 請求以 http://localhost:8080/
開始。
通過添加不帶 .war
后綴的 WAR 文件名來訪問由程序員部署的 WAR 文件:
http://locahost:8080/novels/
如果服務部署在 TOMCAT_HOME
下的一個子目錄中(比如,myapps
),這會在 URL 中反映出來:
http://locahost:8080/myapps/novels/
我會在靠近文章結尾處的測試部分提供這部分的更多細節。
如前所述,我的主頁上有一個包含 Ant 腳本的 ZIP 文件,這個文件可以編譯并且部署網站或者服務。(這個 ZIP 文件中也包含一個 novels.war
的副本。)對于“小說”這個例子,命令的示例(%
是命令行提示符)如下:
% ant -Dwar.name=novels deploy
這個命令首先會編譯 Java 源代碼,并且創建一個可部署的 novels.war
文件,然后將這個文件保存在當前目錄中,再復制到 TOMCAT_HOME/webapps
目錄中。如果一切順利,GET
請求(使用瀏覽器或者命令行工具,比如 curl
)可以用來做一個測試:
% curl http://localhost:8080/novels/
默認情況下,Tomcat 設置為 熱部署:Web 服務器不需要關閉就可以進行部署、更新或者移除一個 web 應用。
讓我們回到“小說”這個例子,不過是在代碼層面。考慮下面的 Novel
類:
package novels; import java.io.Serializable; public class Novel implements Serializable, Comparable<Novel> { static final long serialVersionUID = 1L; private String author; private String title; private int id; public Novel() { } public void setAuthor(final String author) { this.author = author; } public String getAuthor() { return this.author; } public void setTitle(final String title) { this.title = title; } public String getTitle() { return this.title; } public void setId(final int id) { this.id = id; } public int getId() { return this.id; } public int compareTo(final Novel other) { return this.id - other.id; } }
這個類實現了 Comparable
接口中的 compareTo
方法,因為 Novel
實例是存儲在一個線程安全的無序 ConcurrentHashMap
中。在響應查看集合的請求時,“小說”服務會對從映射中提取的集合(一個 ArrayList
)進行排序;compareTo
的實現通過 Novel
的 ID 將它按升序排序。
Novels
類中包含多個實用工具函數:
package novels; import java.io.IOException; import java.io.File; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.BufferedReader; import java.nio.file.Files; import java.util.stream.Stream; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.Collections; import java.beans.XMLEncoder; import javax.servlet.ServletContext; // not in JavaSE import org.json.JSONObject; import org.json.XML; public class Novels { private final String fileName = "/WEB-INF/data/novels.db"; private ConcurrentMap<Integer, Novel> novels; private ServletContext sctx; private AtomicInteger mapKey; public Novels() { novels = new ConcurrentHashMap<Integer, Novel>(); mapKey = new AtomicInteger(); } public void setServletContext(ServletContext sctx) { this.sctx = sctx; } public ServletContext getServletContext() { return this.sctx; } public ConcurrentMap<Integer, Novel> getConcurrentMap() { if (getServletContext() == null) return null; // not initialized if (novels.size() < 1) populate(); return this.novels; } public String toXml(Object obj) { // default encoding String xml = null; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); XMLEncoder encoder = new XMLEncoder(out); encoder.writeObject(obj); encoder.close(); xml = out.toString(); } catch(Exception e) { } return xml; } public String toJson(String xml) { // option for requester try { JSONObject jobt = XML.toJSONObject(xml); return jobt.toString(3); // 3 is indentation level } catch(Exception e) { } return null; } public int addNovel(Novel novel) { int id = mapKey.incrementAndGet(); novel.setId(id); novels.put(id, novel); return id; } private void populate() { InputStream in = sctx.getResourceAsStream(this.fileName); // Convert novel.db string data into novels. if (in != null) { try { InputStreamReader isr = new InputStreamReader(in); BufferedReader reader = new BufferedReader(isr); String record = null; while ((record = reader.readLine()) != null) { String[] parts = record.split("!"); if (parts.length == 2) { Novel novel = new Novel(); novel.setAuthor(parts[0]); novel.setTitle(parts[1]); addNovel(novel); // sets the Id, adds to map } } in.close(); } catch (IOException e) { } } } }
最復雜的方法是 populate
,這個方法從一個包含在 WAR 文件中的文本文件讀取。這個文本文件包括了“小說”的初始集合。要打開此文件,populate
方法需要 ServletContext
,這是一個 Java 映射類型,包含了關于嵌入在 servlet 容器中的 servlet 的所有關鍵信息。這個文本文件有包含了像下面這樣的記錄:
Jane Austen!Persuasion
這一行被解析為兩部分(作者和標題),由感嘆號(!
)分隔。然后這個方法創建一個 Novel
實例,設置作者和標題屬性,并且將“小說”加到容器中,保存在內存中。
Novels
類也有一些實用工具函數,可以將“小說”容器編碼為 XML 或 JSON,取決于發出請求的人所要求的格式。默認是 XML 格式,但是也可以請求 JSON 格式。一個輕量級的 XML 轉 JSON 包提供了 JSON。下面是關于編碼的更多細節。
package novels; import java.util.concurrent.ConcurrentMap; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Arrays; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.beans.XMLEncoder; import org.json.JSONObject; import org.json.XML; public class NovelsServlet extends HttpServlet { static final long serialVersionUID = 1L; private Novels novels; // back-end bean // Executed when servlet is first loaded into container. @Override public void init() { this.novels = new Novels(); novels.setServletContext(this.getServletContext()); } // GET /novels // GET /novels?id=1 @Override public void doGet(HttpServletRequest request, HttpServletResponse response) { String param = request.getParameter("id"); Integer key = (param == null) ? null : Integer.valueOf((param.trim())); // Check user preference for XML or JSON by inspecting // the HTTP headers for the Accept key. boolean json = false; String accept = request.getHeader("accept"); if (accept != null && accept.contains("json")) json = true; // If no query string, assume client wants the full list. if (key == null) { ConcurrentMap<Integer, Novel> map = novels.getConcurrentMap(); Object list = map.values().toArray(); Arrays.sort(list); String payload = novels.toXml(list); // defaults to Xml if (json) payload = novels.toJson(payload); // Json preferred? sendResponse(response, payload); } // Otherwise, return the specified Novel. else { Novel novel = novels.getConcurrentMap().get(key); if (novel == null) { // no such Novel String msg = key + " does not map to a novel.\n"; sendResponse(response, novels.toXml(msg)); } else { // requested Novel found if (json) sendResponse(response, novels.toJson(novels.toXml(novel))); else sendResponse(response, novels.toXml(novel)); } } } // POST /novels @Override public void doPost(HttpServletRequest request, HttpServletResponse response) { String author = request.getParameter("author"); String title = request.getParameter("title"); // Are the data to create a new novel present? if (author == null || title == null) throw new RuntimeException(Integer.toString(HttpServletResponse.SC_BAD_REQUEST)); // Create a novel. Novel n = new Novel(); n.setAuthor(author); n.setTitle(title); // Save the ID of the newly created Novel. int id = novels.addNovel(n); // Generate the confirmation message. String msg = "Novel " + id + " created.\n"; sendResponse(response, novels.toXml(msg)); } // PUT /novels @Override public void doPut(HttpServletRequest request, HttpServletResponse response) { /* A workaround is necessary for a PUT request because Tomcat does not generate a workable parameter map for the PUT verb. */ String key = null; String rest = null; boolean author = false; /* Let the hack begin. */ try { BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream())); String data = br.readLine(); /* To simplify the hack, assume that the PUT request has exactly two parameters: the id and either author or title. Assume, further, that the id comes first. From the client side, a hash character # separates the id and the author/title, e.g., id=33#title=War and Peace */ String[] args = data.split("#"); // id in args[0], rest in args[1] String[] parts1 = args[0].split("="); // id = parts1[1] key = parts1[1]; String[] parts2 = args[1].split("="); // parts2[0] is key if (parts2[0].contains("author")) author = true; rest = parts2[1]; } catch(Exception e) { throw new RuntimeException(Integer.toString(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); } // If no key, then the request is ill formed. if (key == null) throw new RuntimeException(Integer.toString(HttpServletResponse.SC_BAD_REQUEST)); // Look up the specified novel. Novel p = novels.getConcurrentMap().get(Integer.valueOf((key.trim()))); if (p == null) { // not found String msg = key + " does not map to a novel.\n"; sendResponse(response, novels.toXml(msg)); } else { // found if (rest == null) { throw new RuntimeException(Integer.toString(HttpServletResponse.SC_BAD_REQUEST)); } // Do the editing. else { if (author) p.setAuthor(rest); else p.setTitle(rest); String msg = "Novel " + key + " has been edited.\n"; sendResponse(response, novels.toXml(msg)); } } } // DELETE /novels?id=1 @Override public void doDelete(HttpServletRequest request, HttpServletResponse response) { String param = request.getParameter("id"); Integer key = (param == null) ? null : Integer.valueOf((param.trim())); // Only one Novel can be deleted at a time. if (key == null) throw new RuntimeException(Integer.toString(HttpServletResponse.SC_BAD_REQUEST)); try { novels.getConcurrentMap().remove(key); String msg = "Novel " + key + " removed.\n"; sendResponse(response, novels.toXml(msg)); } catch(Exception e) { throw new RuntimeException(Integer.toString(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); } } // Methods Not Allowed @Override public void doTrace(HttpServletRequest request, HttpServletResponse response) { throw new RuntimeException(Integer.toString(HttpServletResponse.SC_METHOD_NOT_ALLOWED)); } @Override public void doHead(HttpServletRequest request, HttpServletResponse response) { throw new RuntimeException(Integer.toString(HttpServletResponse.SC_METHOD_NOT_ALLOWED)); } @Override public void doOptions(HttpServletRequest request, HttpServletResponse response) { throw new RuntimeException(Integer.toString(HttpServletResponse.SC_METHOD_NOT_ALLOWED)); } // Send the response payload (Xml or Json) to the client. private void sendResponse(HttpServletResponse response, String payload) { try { OutputStream out = response.getOutputStream(); out.write(payload.getBytes()); out.flush(); } catch(Exception e) { throw new RuntimeException(Integer.toString(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); } } }
上面的 NovelsServlet
類繼承了 HttpServlet
類,HttpServlet
類繼承了 GenericServlet
類,后者實現了 Servlet
接口:
NovelsServlet extends HttpServlet extends GenericServlet implements Servlet
從名字可以很清楚的看出來,HttpServlet
是為實現 HTTP(S) 上的 servlet 設計的。這個類提供了以標準 HTTP 請求動詞(官方說法,方法)命名的空方法:
doPost
(Post = 創建)
doGet
(Get = 讀取)
doPut
(Put = 更新)
doDelete
(Delete = 刪除)
其他一些 HTTP 動詞也會涉及到。HttpServlet
的子類,比如 NovelsServlet
,會重載相關的 do
方法,并且保留其他方法為空。NovelsServlet
重載了七個 do
方法。
每個 HttpServlet
的 CRUD 方法都有兩個相同的參數。下面以 doPost
為例:
public void doPost(HttpServletRequest request, HttpServletResponse response) {
request
參數是一個 HTTP 請求信息的映射,而 response
提供了一個返回給請求者的輸出流。像 doPost
的方法,結構如下:
讀取 request
信息,采取任何適當的措施生成一個響應。如果該信息丟失或者損壞了,就會生成一個錯誤。
使用提取的請求信息來執行適當的 CRUD 操作(在本例中,創建一個 Novel
),然后使用 response
輸出流為請求者編碼一個適當的響應。在 doPost
例子中,響應就是已經成功生成一個新“小說”并且添加到容器中的一個確認。當響應被發送后,輸出流就關閉了,同時也將連接關閉了。
HTTP 請求的格式相對比較簡單。下面是一個非常熟悉的 HTTP 1.1 的格式,注釋由雙井號分隔:
GET /novels ## start lineHost: localhost:8080 ## header elementAccept-type: text/plain ## ditto...[body] ## POST and PUT only
第一行由 HTTP 動詞(在本例中是 GET
)和以名詞(在本例中是 novels
)命名目標資源的 URI 開始。報頭中包含鍵-值對,用冒號分隔左面的鍵和右面的值。報頭中的鍵 Host
(大小寫敏感)是必須的;主機名 localhost
是當前機器上的本地符號地址,8080
端口是 Tomcat web 服務器上等待 HTTP 請求的默認端口。(默認情況下,Tomcat 在 8443 端口上監聽 HTTP 請求。)報頭元素可以以任意順序出現。在這個例子中,Accept-type
報頭的值是 MIME 類型 text/plain
。
一些請求(特別是 POST
和 PUT
)會有報文,而其他請求(特別是 GET
和 DELETE
)沒有。如果有報文(可能為空),以兩個換行符將報頭和報文分隔開;HTTP 報文包含一系列鍵-值對。對于無報文的請求,比如說查詢字符串,報頭元素就可以用來發送信息。下面是一個用 ID 2 對 /novels
資源的 GET
請求:
GET /novels?id=2
通常來說,查詢字符串以問號開始,并且包含一個鍵-值對,盡管這個鍵-值可能值為空。
帶有 getParameter
和 getParameterMap
等方法的 HttpServlet
很好地回避了有報文和沒有報文的 HTTP 請求之前的差異。在“小說”例子中,getParameter
方法用來從 GET
、POST
和 DELETE
方法中提取所需的信息。(處理 PUT
請求需要更底層的代碼,因為 Tomcat 沒有提供可以解析 PUT
請求的參數映射。)下面展示了一段在 NovelsServlet
中被重載的 doPost
方法:
@Override public void doPost(HttpServletRequest request, HttpServletResponse response) { String author = request.getParameter("author"); String title = request.getParameter("title"); ...
對于沒有報文的 DELETE
請求,過程基本是一樣的:
@Override public void doDelete(HttpServletRequest request, HttpServletResponse response) { String param = request.getParameter("id"); // id of novel to be removed ...
doGet
方法需要區分 GET
請求的兩種方式:一種是“獲得所有”,而另一種是“獲得某一個”。如果 GET
請求 URL 中包含一個鍵是一個 ID 的查詢字符串,那么這個請求就被解析為“獲得某一個”:
http://localhost:8080/novels?id=2 ## GET specified
如果沒有查詢字符串,GET
請求就會被解析為“獲得所有”:
http://localhost:8080/novels ## GET all
“小說”服務的設計反映了像 Tomcat 這樣基于 Java 的 web 服務器是如何工作的。在啟動時,Tomcat 構建一個線程池,從中提取請求處理程序,這種方法稱為 “每個請求一個線程” 模型。現在版本的 Tomcat 使用非阻塞 I/O 來提高個性能。
“小說”服務是作為 NovelsServlet
類的單個實例來執行的,該實例也就維護了一個“小說”集合。相應的,也就會出現競態條件,比如出現兩個請求同時被處理:
一個請求向集合中添加一個新“小說”。
另一個請求獲得集合中的所有“小說”。
這樣的結果是不確定的,取決與 讀 和 寫 的操作是以怎樣的順序進行操作的。為了避免這個問題,“小說”服務使用了線程安全的 ConcurrentMap
。這個映射的關鍵是生成了一個線程安全的 AtomicInteger
。下面是相關的代碼片段:
public class Novels { private ConcurrentMap<Integer, Novel> novels; private AtomicInteger mapKey; ...
默認情況下,對客戶端請求的響應被編碼為 XML。為了簡單,“小說”程序使用了以前的 XMLEncoder
類;另一個包含更豐富功能的方式是使用 JAX-B 庫。代碼很簡單:
public String toXml(Object obj) { // default encoding String xml = null; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); XMLEncoder encoder = new XMLEncoder(out); encoder.writeObject(obj); encoder.close(); xml = out.toString(); } catch(Exception e) { } return xml; }
Object
參數要么是一個有序的“小說” ArraList
(用以響應“獲得所有”請求),要么是一個 Novel
實例(用以響應“獲得一個”請求),又或者是一個 String
(確認消息)。
如果 HTTP 請求報頭指定 JSON 作為所需要的類型,那么 XML 就被轉化成 JSON。下面是 NovelsServlet
中的 doGet
方法中的檢查:
String accept = request.getHeader("accept"); // "accept" is case insensitive if (accept != null && accept.contains("json")) json = true;
Novels
類中包含了 toJson
方法,可以將 XML 轉換成 JSON:
public String toJson(String xml) { // option for requester try { JSONObject jobt = XML.toJSONObject(xml); return jobt.toString(3); // 3 is indentation level } catch(Exception e) { } return null; }
NovelsServlet
會對各種類型進行錯誤檢查。比如,POST
請求應該包含新“小說”的作者和標題。如果有一個丟了,doPost
方法會拋出一個異常:
if (author == null || title == null) throw new RuntimeException(Integer.toString(HttpServletResponse.SC_BAD_REQUEST));
SC_BAD_REQUEST
中的 SC
代表的是 狀態碼,BAD_REQUEST
的標準 HTTP 數值是 400。如果請求中的 HTTP 動詞是 TRACE
,會返回一個不同的狀態碼:
public void doTrace(HttpServletRequest request, HttpServletResponse response) { throw new RuntimeException(Integer.toString(HttpServletResponse.SC_METHOD_NOT_ALLOWED)); }
用瀏覽器測試 web 服務會很不順手。在 CRUD 動詞中,現代瀏覽器只能生成 POST
(創建)和 GET
(讀取)請求。甚至從瀏覽器發送一個 POST
請求都有點不好辦,因為報文需要包含鍵-值對;這樣的測試通常通過 HTML 表單完成。命令行工具,比如說 curl,是一個更好的選擇,這個部分展示的一些 curl
命令,已經包含在我網站的 ZIP 文件中了。
下面是一些測試樣例,沒有展示相應的輸出結果:
% curl localhost:8080/novels/% curl localhost:8080/novels?id=1% curl --header "Accept: application/json" localhost:8080/novels/
第一條命令請求所有“小說”,默認是 XML 編碼。第二條命令請求 ID 為 1 的“小說”,XML 編碼。最后一條命令通過 application/json
添加了 Accept
報頭元素,作為所需要的 MIME 類型。“獲得一個”命令也可以用這個報頭。這些請求用了 JSON 而不是 XML 編碼作為響應。
下面兩條命令在集合中創建了一個新“小說”,并且確認添加了進去:
% curl --request POST --data "author=Tolstoy&title=War and Peace" localhost:8080/novels/% curl localhost:8080/novels?id=4
curl
中的 PUT
命令與 POST
命令相似,不同的地方是 PUT
的報文沒有使用標準的語法。在 NovelsServlet
中關于 doPut
方法的文檔中有詳細的介紹,但是簡單來說,Tomcat 不會對 PUT
請求生成合適的映射。下面是一個 PUT
命令和確認命令的的例子:
% curl --request PUT --data "id=3#title=This is an UPDATE" localhost:8080/novels/% curl localhost:8080/novels?id=3
第二個命令確認了集合已經更新。
最后,DELETE
命令會正常運行:
% curl --request DELETE localhost:8080/novels?id=2% curl localhost:8080/novels/
這個請求是刪除 ID 為 2 的“小說”。第二個命令會顯示剩余的“小說”。
盡管官方規定它是可選的,web.xml
配置文件是一個生產級別網站或服務的重要組成部分。這個配置文件可以配置獨立于代碼的路由、安全性,或者網站或服務的其他功能。“小說”服務的配置通過為該服務的請求分配一個 URL 模式來配置路由:
<xml version = "1.0" encoding = "UTF-8"> <web-app> <servlet> <servlet-name>novels</servlet-name> <servlet-class>novels.NovelsServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>novels</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
servlet-name
元素為 servlet 全名(novels.NovelsServlet
)提供了一個縮寫(novels
),然后這個名字在下面的 servlet-mapping
元素中使用。
回想一下,一個已部署服務的 URL 會在端口號后面有 WAR 文件的文件名:
http://localhost:8080/novels/
端口號后斜杠后的 URI,是所請求資源的“路徑”,在這個例子中,就是“小說”服務。因此,novels
出現在了第一個單斜杠后。
在 web.xml
文件中,url-patter
被指定為 /*
,代表 “以 /novels
為起始的任意路徑”。假設 Tomcat 遇到了一個不存在的 URL,像這樣:
http://localhost:8080/novels/foobar/
web.xml
配置也會指定這個請求被分配到“小說” servlet 中,因為 /*
模式也包含 /foobar
。因此,這個不存在的 URL 也會得到像上面合法路徑的相同結果。
生產級別的配置文件可能會包含安全相關的信息,包括連接級別和用戶角色。即使在這種情況下,配置文件的大小也只會是這個例子中的兩到三倍大。
到此,相信大家對“怎么理解用Java實現的超輕量級RESTful Web服務”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。