Viết các dịch vụ REST

Tạo các dịch vụ REST với công nghệ Java và Atom Publishing Protocol (Giao thức Xuất bản Atom)

Hướng dẫn này bàn luận về các khái niệm của REST và Atom Publishing Protocol (APP - Giao thức Xuất bản Atom) và trình bày cách áp dụng chúng cho các dịch vụ. Nó cũng trình bày cách sử dụng công nghệ Java™ để thực hiện các dịch vụ dựa trên REST/APP.

J. Jeffrey Hanson, Kiến trúc sư trưởng, eReinsure.com, Inc.

Jeff Hanson đã có hơn 20 năm kinh nghiệm trong ngành công nghiệp phần mềm, là kỹ sư cấp cao cho cổng của Microsoft Windows của dự án OpenDoc, là kiến trúc sư trưởng của khung công tác Route 66 của Novell và là kiến trúc sư trưởng của công ty eReinsure.com, Inc, nơi ông hướng dẫn việc thiết kế và thực hiện khung công tác và nền tảng cho các hệ thống tái bảo hiểm dựa trên nền J2EE. Jeff hiện là Giám đốc kỹ thuật (CTO) của công ty Max Software, Inc, nơi ông chỉ đạo các nỗ lực để cung cấp các ứng dụng máy tính để bàn và ứng dụng doanh nghiệp và các nền tảng cho việc kiểm soát an toàn Internet và kiểm soát của cha mẹ đối với Internet. Jeff là tác giả của rất nhiều bài báo và sách



20 12 2007 (Xuất bản lần đầu tiên vào ngày 18 12 2009)

Trước khi bạn bắt đầu

Hãy tìm hiểu hướng dẫn này mang lại những gì và làm thế nào để sử dụng nó tốt nhất.

Về hướng dẫn này

Hướng dẫn này bàn luận về các khái niệm của việc Chuyển giao Trạng thái Đại diện (Representation State Transfer) (REST) và Giao thức Xuất bản Atom (APP) và trình bày cách áp dụng chúng vào các dịch vụ. Ngoài ra, bạn còn tìm hiểu cách thực hiện dịch vụ dựa trên REST/APP với công nghệ Java.

Các mục tiêu

Trong hướng dẫn này, bạn:

  • Tìm hiểu các khái niệm cơ sở của REST và các khái niệm cơ sở đằng sau APP.
  • Áp dụng các công nghệ này trong bất kỳ tổ chức nào để gửi và nhận các yêu cầu và phản hồi HTTP qua một hệ thống dựa trên tiện ích Java.
  • Cho phép truy cập và thay đổi dữ liệu nguồn cho các bài phát thanh trên mạng, các mục nhập blog, lưu trữ hình ảnh, các mục nhập lịch, v.v....

Các điều kiện tiên quyết

Hướng dẫn này được viết cho các lập trình viên Java, những người có các kỹ năng và kinh nghiệm từ mức trung cấp đến chuyên gia. Bạn phải có một sự quen biết tổng hợp việc sử dụng các tiện ích Java và một kiến thức làm việc về ngôn ngữ lập trình Java. Để tải về tất cả mã nguồn đối với hướng dẫn này, xin xem mục Tải về.

Yêu cầu về hệ thống

Để chạy các thí dụ trong hướng dẫn này, bạn cần một vùng chứa tiện ích Java.


Chuyển giao Trạng thái đại diện

REST là một tập hợp các nguyên tắc kiến trúc và một kiểu kiến trúc phần mềm để xây dựng các hệ thống dùng mạng (network-enabled system) dựa trên các cơ cấu mà định nghĩa và truy cập các tài nguyên, chẳng hạn như WWW (World Wide Web). Thuật ngữ REST, được Roy Fielding định nghĩa trong luận án của ông (xem Tài nguyên), thường được dùng một cách khá lỏng lẻo để mô tả một khung làm việc để chuyển tải dữ liệu qua một giao thức chẳng hạn như HTTP mà không phải bổ sung các lớp ngữ nghĩa hoặc quản lý phiên làm việc.

REST định nghĩa một sự phân cách nghiêm ngặt của các mối liên hệ giữa các thành phần có tham gia vào một hệ thống khách-chủ mà đơn giản hoá việc thực hiện các vai yêu cầu. REST cũng phấn đấu để đơn giản hoá ngữ nghĩa truyền thông trong một hệ thống được nối mạng để làm tăng khả năng mở rộng và cải thiện hiệu năng. REST liên quan đến yêu cầu tự quản, giữa các thành phần tham gia trong một trao đổi thông điệp mà ngụ ý các yêu cầu đó phải gồm toàn bộ các thông tin mà một máy khách hoặc máy chủ yêu cầu để hiểu được ngữ cảnh của yêu cầu. Trong một hệ thống dựa trên REST, bạn sử dụng các tập hợp tối thiểu các yêu cầu khả dĩ để trao đổi các kiểu phương tiện truyền thông chuẩn.

Nguyên tắc REST sử dụng các bộ định danh tài nguyên đồng bộ (uniform resource identifiers) (URIs) để định vị và truy cập một đại diện đã cho của tài nguyên. Đại diện Nguồn, gọi là Trạng thái đại diện, có thể được tạo, lấy ra, sửa đổi, và xoá bỏ. Ví dụ, bạn có thể áp dụng REST cho tài liệu xuất bản để làm cho các tài liệu này sẵn có cho các độc giả. Tại bất kỳ thời điểm nào cho trước, nhà xuất bản có thể trình bày các URL Web để các độc giả có thể truy cập thông tin (trạng thái đại diện) về các tài liệu của nhà xuất bản. Các độc giả của tài liệu chỉ cần biết các URL để đọc thông tin tài liệu, và nếu được ủy quyền thì sửa đổi thông tin.

Như Roy Fielding đã mô tả, một trong các nguyên tắc hạn chế nội dung của REST là ở chỗ nó có thể khai thác các công nghệ, chuẩn, giao thức hiện hành liên quan đến Web, chẳng hạn như HTTP. Sự trông mong này về các công nghệ và giao thức hiện hành làm cho REST dễ học hơn và sử dụng đơn giản hơn phần lớn các chuẩn thông báo dựa trên Web khác, vì các chi phí phụ bổ sung đòi hỏi ít hơn, cho phép việc trao đổi thông tin hiệu quả.

Một hội thoại dựa trên REST, theo Fielding, hoạt động trong các hội thoại phi trạng thái, bằng cách ấy tạo cho nó một bộ xúc tiến chủ yếu cho các công nghệ dựa trên thuê bao, chẳng hạn như RSS, RDF, OWL, và Atom, trong đó nội dung được đưa vào cho các khách hàng thuê bao trước.

Các thực thể chủ yếu của REST

REST định nghĩa các thực thể chủ yếu sau đây:

  • Các phần tử dữ liệu: Dữ liệu, các bộ định danh (các URL và URI), và các đại diện của dữ liệu chẳng hạn như tài liệu HTML, tài liệu XML, và hình ảnh.
  • Các thành phần: Các máy chủ nguồn chẳng hạn như Apache httpd và Các Dịch vụ Thông tin Internet Microsoft® (IIS), các cổng vào chẳng hạn như Squid và CGI, các proxy chẳng hạn như Gauntlet và Netscape, và các đại lý người sử dụng chẳng hạn như các trình duyệt Web hoặc các thiết bị lưu động.
  • Các đầu nối: Các đầu nối khách hàng chẳng hạn như libwww, các đầu nối máy chủ chẳng hạn như NSAPI, các cache, chẳng hạn như một cache trình duyệt, và một số khác.

Hình 1 minh hoạ một số thực thể REST chủ yếu và cách chúng có thể tương tác trong một hệ thống được nối mạng điển hình. Chú ý rằng các yêu cầu được truyền và nhận bởi các đầu nối mà được nối với một thực thể cho trước, chẳng hạn như một máy chủ hoặc cơ sở dữ liệu. Các đầu nối xử lý các nhiệm vụ chuyển tải cho một giao thức cho trước, bằng cách ấy cho phép một thực thể truyền hoặc nhận các yêu cầu theo cách thức tương tự qua bất kỳ giao thức nào, cho đến khi một đầu nối có mặt cho giao thức mong đợi đó.

Hình 1. Tương tác trong số các thực thể REST
Tương tác trong số các thực thể REST

Như bạn đã thấy, các đầu nối cho một hệ thống như vậy biểu hiện như là các cổng giao thức, cho phép việc truyền thông giữa các thành phần. Lưu ý rằng bất kỳ thành phần nào trong một hệ thống REST cũng có thể có nhiều đầu nối máy khách và/hoặc máy chủ để xử lý việc truyền thông giữa các thành phần khác và chính nó.

REST và HTTP

Theo luận án của Fielding, trong một hệ thống REST dựa trên HTTP, bạn phải sử dụng cá phương thức HTTP chuẩn — có nghĩa là GET, PUT, POST, và DELETE— để truy cập trạng thái đại diện cho tài nguyên:

  • GET: Sử dụng phương thức này để chuyển giao trạng thái đại diện hiện tại của một tài nguyên từ nhà xuất bản đến người tiêu dùng.
  • PUT: Sử dụng phương thức này để chuyển giao trạng thái đại diện được sửa đổi của một tài nguyên từ người tiêu dùng đến nhà xuất bản.
  • POST: Sử dụng phương thức này để chuyển giao trạng thái đại diện mới của một tài nguyên từ người tiêu dùng đến nhà xuất bản.
  • DELETE: Sử dụng phương thức này để chuyển giao thông tin cần có để thay đổi trạng thái đại diện của một tài nguyên sang xóa.

Các công nghệ máy chủ Java, chẳng hạn như các tiện ích và các trang JavaServer (JSP™), hỗ trợ các phương thức HTTP chuẩn. Các mục sau đây bàn luận về cách mở rộng các công nghệ này để hỗ trợ APP từ một hệ thống dựa trên REST bằng cách sử dụng công nghệ Java.


Giao thức Xuất bản Atom

APP là một giao thức dựa trên HTTP để xuất bản, xoá, và cập nhật các tài nguyên. APP nhấn mạnh khái niệm của việc sử dụng các phép toán chủ yếu mà giao thức HTTP cung cấp (chẳng hạn như GET, PUT, POST, và DELETE) để chuyển giao các cá thể của các tài liệu nội quan và thu thập các tài liệu chứa các loại tài nguyên và nguồn cấp sẵn có để xuất bản, cập nhật, v.v... Sau khi các tài nguyên và nguồn cấp được phát hiện, các URI được liên kết của chúng được dùng vào việc xuất bản và sửa đổi. Các nguồn cung cấp thành viên chứa các thứ như các mục nhập blog, các bài phát thanh trên mạng, tài liệu trang mạng biên tập tự do, và các sự kiện trên lịch. APP cũng đã được nghiên cứu và sử dụng như một API mặt trước đối với dải rộng các dịch vụ lưu trữ dữ liệu, chẳng hạn như các máy chủ cơ sở dữ liệu, các dịch vụ quản lý tài liệu và nội dung, và các kho chứa phần mềm.

APP xây dựng trên Định dạng Atom Syndication để chính thức hoá nhiều cơ chế sử dụng trong việc trao đổi nội dung giàu về mặt ngữ nghĩa bằng cách sử dụng các khái niệm và công nghệ REST. Giao thức hoạt động trên các vùng chứa của các tài nguyên Web gọi là collections. Các sưu tập được trình bày để khám phá ra trong các tài liệu nội quan. Tất cả các sưu tập đều chứa tài nguyên bộ phận, chúng là các mục tiêu cuối cùng để tạo, truy cập, thay đổi, và xoá bỏ. Các tương tác với một sưu tập và các tài nguyên bộ phận của nó dựa trên cơ sở các động từ HTTP phổ biến:

  • GET: Dùng để lấy ra một đại diện của một sưu tập hoặc tài nguyên bộ phận.
  • POST: Dùng để tạo một tài nguyên bộ phận mới.
  • PUT: Dùng để cập nhật một tài nguyên bộ phận.
  • DELETE: Dùng để loại bỏ một tài nguyên bộ phận.

Các thực thể cơ bản của APP

APP định nghĩa các thực thể cơ bản sau:

  • Tài liệu nội quan (Introspection document): Tài liệu nội quan có một ứng dụng/atomserv+xml kiểu lớp giữa (media-type) và mô tả các vùng làm việc, là các nhóm được máy chủ định nghĩa tùy ý của các sưu tập. Một sưu tập có thể xuất hiện trong nhiều vùng làm việc. Liệt kê 1 là một thí dụ về một tài liệu nội quan điển hình.
    Liệt kê 1. Một tài liệu nội quan điển hình
    <?xml version="1.0" encoding="utf-8" ?> 
    <service xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom">
    - <workspace>
        <atom:title>Main Site</atom:title> 
        - <collection href="http://localhost:8080/atompub/services/collections/main">
            <atom:title>My Main Page</atom:title> 
            <accept>application/atom+xml;type=entry</accept> 
            <categories fixed="yes" /> 
          </collection>
          <collection href="http://localhost:8080/atompub/services/collections/pics">
            <atom:title>My Pictures</atom:title> 
            <accept>*/*</accept> 
            <categories fixed="yes" /> 
          </collection>
      </workspace>
    - <workspace>
        <atom:title>Documents</atom:title> 
        - <collection href="http://localhost:8080/atompub/services/collections/docs">
            <atom:title>My Documents</atom:title> 
            <accept>application/atom+xml;type=entry</accept> 
            <categories fixed="yes" /> 
          </collection>
      </workspace>
    </service>

    Tài liệu nội quan được minh họa nói trên định nghĩa một dịch vụ với hai vùng làm việc. Vùng làm việc đầu tiên, gọi là “Trang Web Chính” (Main Site), định nghĩa hai sưu tập tên là “Trang chính Của Tôi” (My Main Page) và “Hình ảnh Của Tôi” (My Pictures). Sưu tập “Trang chính Của Tôi” có tại http://localhost:8080/atompub/services/collections/main và sưu tập “Hình ảnh Của Tôi” có tại http://localhost:8080/atompub/services/collections/pics. Vùng làm việc thứ hai, “Các Tài liệu”, chứa một sưu tập tên là “Các tài liệu Của Tôi” và có tại http://localhost:8080/atompub/services/collections/docs.

  • URI: Một bộ định danh tài nguyên đồng bộ.
  • IRI: Một bộ Định danh Tài nguyên được Quốc tế hoá (xem Tài nguyên).
  • Tài nguyên bộ phận: Một đối tượng hoặc dịch vụ dữ liệu như là một tài nguyên đầu vào hoặc một tài nguyên trung gian mà một IRI hoặc URI đặt ở đó. Các tài nguyên đầu vào được trình bày như là tài liệu đầu vào Atom. Các tài nguyên trung gian có thể có các đại diện theo bất kỳ kiểu phương tiện truyền thông nào. Một tài nguyên phương tiện truyền thông được mô tả trong một sưu tập sử dụng một mục phương tiện truyền thông liên kết.
  • Đại diện: Chế độ của một tài nguyên cho trước được chuyển tải bởi một yêu cầu hoặc một phản hồi.
  • Sưu tập: Một vùng chứa của các tài nguyên bộ phận được xác định bởi một URI duy nhất. Các sưu tập được trình bày như các nguồn cấp Atom. Để tạo các mục mới trong một sưu tập, máy khách gửi các yêu cầu POST HTTP đến URI của sưu tập. Các mục mới được chỉ định một URI duy nhất để được sử dụng như các tham chiếu địa chỉ. Để sửa đổi các mục trong một sưu tập, một máy khách lấy ra tài nguyên bằng cách sử dụng URI của nó, thực hiện các sửa đổi, sau đó sử dụng PUT để chuyển đại diện được sửa đổi của tài nguyên đến máy chủ. Để loại bỏ tài nguyên bộ phận từ một sưu tập, chạy một truy vấn DELETE HTTP đến URI của tài nguyên bộ phận. Tất cả các bộ phận của một sưu tập phải có một đặc tính updated (được cập nhật) theo đó một tài liệu sưu tập có trật tự.
  • Vùng làm việc: Một tập hợp có tên của các sưu tập.
  • Tài liệu/phần tử Dịch vụ: Phần tử cấp cao nhất của một tài liệu nội quan, mô tả địa chỉ và các khả năng của một hoặc nhiều sưu tập, tập hợp lại thành các vùng làm việc.
  • Tài liệu kiểu loại: Một tài liệu mô tả các kiểu loại được phép trong một sưu tập.

Các mục sau đây bàn luận về cách mở rộng các công nghệ Java để hỗ trợ APP từ một hệ thống dựa trên REST.


Đi sâu

Bạn có thể sử dụng ngôn ngữ lập trình Java để áp dụng các nguyên tắc REST vào một hệ thống mà chuyển tải các tài nguyên qua giao thức HTTP. Bạn có thể sử dụng APP để mở rộng một hệ thống như vậy để tạo điều kiện thuận lợi cho truy cập và vận dụng tài nguyên bằng cách sử dụng các mục blog, các bài phát thanh trên mạng, tài liệu trang mạng biên tập tự do, các mục trên lịch, và những thứ tương tự. Hướng dẫn này chỉ cho bạn cách áp dụng các khái niệm cơ bản của REST và APP trong một ứng dụng Web để chuyển tải các tài nguyên qua các truy vấn và phản hồi HTTP bằng cách sử dụng một hệ thống dựa trên tiện ích Java.

Đặc tả giao thức Atom định nghĩa một tài liệu XML mà một máy khách có thể sử dụng để tự xem xét bên trong điểm cuối APP. Một tài liệu nội quan chứa một danh sách tất cả các sưu tập sẵn có tại một điểm cuối của URI cho trước. Do đó, bước đầu tiên sử dụng bất kỳ dịch vụ kích hoạt APP nào là quyết định các sưu tập sẵn có tại URI của điểm cuối dịch vụ và các kiểu nào của tài nguyên bộ phận mà mỗi sưu tập có thể chứa. Để lấy ra tài liệu nội quan, một máy khách gửi một truy vấn GET đến máy chủ. Sau đó máy chủ phản hồi lại khách hàng với một tài liệu nội quan có chứa các URI địa chỉ của các sưu tập mà dịch vụ cung cấp.

Mỗi phần tử sưu tập trong một tài liệu nội quan thể hiện vùng chứa mà trong đó thông tin về mỗi tài nguyên bộ phận thuộc bộ sưu tập được lưu lại. Mỗi sưu tập trong một tài liệu nội quan có URI duy nhất. Việc chạy một truy vấn HTTP GET cho URI đó sẽ trả về một tài liệu về Atom có chứa thông tin về tài nguyên bộ phận có trong sưu tập.

Để tạo các mục tài nguyên bộ phận mới trong sưu tập, máy khách gửi các truy vấn HTTP POST đến sưu tập URI cùng với một trạng thái đại diện của các tài nguyên bộ phận đã được bổ sung vào sưu tập. Các lối vào tài nguyên bộ phận mới có URI riêng duy nhất mà bạn có thể truy vấn để lấy ra trạng thái đại diện của mỗi lỗi vào tài nguyên bộ phận. Để sửa đổi một mục tài nguyên bộ phận, một máy khách lấy ra tài nguyên bằng cách chạy một truy vấn HTTP GET đến URI của tài nguyên bộ phận, thực hiện các sửa đổi mong muốn cho trạng thái đại diện của tài nguyên mà lấy truy vấn GET trả về, sau đó gửi trạng thái đại diện đã được sửa đổi trở lại sưu tập bằng cách sử dụng một truy vấn HTTP PUT. Việc xoá bỏ lối vào tài nguyên bộ phận khỏi sưu tập là dễ làm khi máy khách chạy một truy vấn HTTP DELETE đến URI tài nguyên bộ phận thích hợp.

Một ứng dụng giao thức xuất bản Atom đơn giản sử dụng REST

Ứng dụng mẫu cho hướng dẫn này được thiết kế để sử dụng một tiện ích bộ điều khiển để xử lý các truy vấn đến và chạy kết quả các phản hồi. Các truy vấn đến được gửi đến một khung làm việc dịch vụ dựa trên REST, cho phép dùng các phép toán trên các tài liệu nội quan, các sưu tập, và các tài nguyên bộ phận APP.

Khung làm việc APP dựa trên REST chứa các lớp và giao diện như trong Hình 2.

Hình 2. Các thành phần của khung làm việc ứng dụng REST/APP
Các thành phần của khung làm việc ứng dụng REST/APP

Như sơ đồ lớp này minh hoạ, một tiện ích FrontController là điểm trung tâm của tham chiếu đối với hầu hết các thực thể sơ cấp REST. Khi tiện ích FrontController nhận các truy vấn, nó tương tác với các đầu nối để lấy ra các đối tượng ServerComponent mà được dùng để định vị các đối tượng BusinessService mà sẽ dùng các logic nghiệp vụ cho từng truy vấn. Các tài nguyên được xác định bởi giao diện Representation mà được thực hiện bằng cách sử dụng các lớp có trong gói javax.activation.

Các mục sau đây bàn luận về cách các thực thể APP và các thực thể sơ cấp REST tương tác trong khung làm việc mẫu cùng với các thành phần Java riêng mà thể hiện từng thực thể.

Các thành phần REST

Các thành phần REST được định nghĩa bởi vai trò của chúng trong một hệ thống cho trước. Để lặp lại, các thành phần điển hình trong một hệ thống REST là:

  • Các nhân tố người sử dụng: Các thành phần mà khởi xướng các truy vấn đến một số dạng của thành phần máy chủ sẽ phản hồi trở lại nhân tố người dùng. Một nhân tố người sử dụng có thể là một thiết bị lưu động, một trình duyệt Web, v.v....
  • Các máy chủ nguồn: Một máy chủ nguồn bao quanh một vùng tên cho trước đối với một sưu tập của các tài nguyên. Một máy chủ nguồn quản lý trạng thái đại diện của các tài nguyên mà nó chứa. Nó nhận các truy vấn từ các nhân tố người sử dụng và thực hiện các hành động cần thiết trên các sưu tập đích hoặc các tài nguyên bộ phận. Sau đó máy chủ nguồn phản hồi đến nhân tố người sử dụng với một tài liệu sưu tập hoặc trạng thái đại diện cho một tài nguyên.

Trong ngôn ngữ Java, bạn có thể trình bày một thành phần REST bằng cách sử dụng một giao diện đánh dấu (marker interface):

public interface Component
{
}

Một thành phần máy chủ trong ngôn ngữ Java sẽ lộ ra các phương thức và đặc tính theo đó các ngữ cảnh tài nguyên được tham chiếu. Việc này được hiển thị trong Liệt kê 2.

Liệt kê 2. Giao diện REST ServerComponent (ngôn ngữ Java)
public abstract class ServerComponent
  implements Component
{
  private java.util.HashMap<String, Context> contexts =
    new java.util.HashMap<String, Context>();
  
  abstract public Context addContext(String contextRootPath,
                                     String contextPath);
  
  public java.util.Iterator<String> getContextPaths()
  {
    return contexts.keySet().iterator();
  }
  
  public Context getContext(String contextPath)
  {
    return contexts.get(contextPath);
  }
  
  protected java.util.HashMap<String, Context> getContexts()
  {
    return contexts;
  }
}

Hãy để ý cách lớp ServerComponent ánh xạ một ngữ cảnh cho trước sang một đường dẫn gốc/tài liệu. Mỗi thành phần máy chủ riêng lẻ sử dụng đường dẫn gốc/tài liệu khi nó cần để ánh xạ sang một ngữ cảnh cho trước. Thí dụ, một thành phần máy chủ đặc trưng HTTP sẽ quản lý các ngữ cảnh đặc trưng HTTP, như trong Liệt kê 3.

Liệt kê 3. Giao diện REST ServerComponent (HTTP)
;
public Context addContext(String contextRootPath, String contextPath)
{
  Context context = getContexts().get(contextPath);
  if (context == null)
  {
    context = new HTTPContext(contextRootPath, contextPath);
    getContexts().put(contextPath, context);
  }
  
  return context;
}

Một ngữ cảnh đặc trưng HTTP sẽ thực hiện một giao diện xác định mỗi phương thức HTTP sơ cấp và đảm bảo phương tiện để gửi đi các truy vấn hoạt động nhằm mục tiêu là các sưu tập và đại diện tài nguyên bộ phận. Liệt kê 4 cho một thí dụ về một việc thực hiện phương thức HTTP GET. Chi tiết về logic nghiệp vụ đối với một dịch vụ miền riêng hoặc nguồn cấp tài nguyên bộ phận được bao gói trong các cài đặt dịch vụ kinh doanh riêng.

Liệt kê 4. Thí dụ về Ngữ cảnh HTTP REST
public Representation handleGet(Request request)
  throws ContextRequestException
{
  String uri = ((HTTPRequest)request).getRequest().getRequestURI();
  if (uri.startsWith(contextPath))
  {
    uri = reqURI.substring(contextPath.length());
  }

  BusinessService businessService =
    ServiceLocator.locateService(contextRootPath, contextPath, uri);

  Representation representation = null;
  
  try
  {
    representation = businessService.read(request);
  }
  catch (ServiceExecutionException e)
  {
    e.printStackTrace();
    throw new ContextRequestException(e);
  }
  
  return representation;
}

Trong Liệt kê 4, một truy vấn GET được dùng trong lần đầu tiên trích lục URI từ truy vấn và sau đó sử dụng URI để định vị dịch vụ kinh doanh mong muốn. Sau đó dịch vụ kinh doanh được dẫn ra để thực hiện truy vấn.

Các thành phần REST được ghép với nhau một cách lỏng lẻo và tương tác lẫn nhau qua các đối tượng đầu nối.

Các đầu nối REST

Một đầu nối trong REST bộc lộ ra một giao diện chung, cho phép xác định các đặc tính và phương thức để cho phép việc truyền thông giữa các thành phần REST. Một đầu nối thể hiện một cổng hoặc điểm cuối của một giao thức truyền thông. Một cài đặt đầu nối có thể thể hiện một kết nối máy khách hoặc một kết nối máy chủ. Các nhân tố người sử dụng dùng các đầu nối máy khách để khởi đầu các truy vấn đến một máy chủ nguồn. Các máy chủ nguồn trông cậy vào các đầu nối máy chủ để xử lý các truy vấn đến từ các nhân tố người sử dụng và phản ứng với trạng thái đại diện cho các sưu tập hoặc các tài nguyên bộ phận.

Giao diện hiển thị trong Liệt kê 5 minh hoạ một đầu nối định nghĩa một phương thức để bổ sung một thành phần máy chủ tại một cổng cho trước.

Liệt kê 5. Giao diện đầu nối REST
public interface Connector
{
  public ServerComponent addServer(int port);
}

Một lớp chế tạo (factory) có thể chứng tỏ là hữu ích đối với quản lý các đầu nối có tính giao thức. Lớp factory có thể kết hợp đầu nối chế tạo (factory connector) khi trạng thái ra lệnh. Liệt kê 6 cho một thí dụ về một lớp factory đầu nối.

Liệt kê 6. Thí dụ về chế tạo đầu nối REST
;
public static Connector getConnector(Protocol protocol)
    throws ConnectorException
  {
    String key = protocol.getScheme();
    Connector instance = instances.get(key);
    
    if (instance == null)
    {
      if (key.equalsIgnoreCase(Protocol.HTTP.getScheme()))
      {
        instance = new HTTPConnector();
        instances.put(key, instance);
      }
      else
      {
        throw new ConnectorException("Invalid protocol: "
                                     + protocol.getScheme());
      }
    }
    
    return instance;
  }

Một giao diện đầu nối cho một đầu nối máy chủ đặc trưng HTTP định nghĩa các đặc tính và phương thức để bổ sung các thành phần máy chủ đặc trưng HTTP cho các cổng riêng, như trong Liệt kê 7.

Liệt kê 7. Thí dụ về đầu nối máy chủ REST
  public ServerComponent addServer(int port)
  {
    return new HTTPServerComponent(port);
  }

Các đầu nối cho phép truyền thông giữa các nhân tố người sử dụng và các máy chủ nguồn. Các máy chủ nguồn, đại diện bởi các thành phần máy chủ, chứa và quản lý các tài nguyên bộ phận và sẽ được bàn luận trong mục tiếp theo.


Các đại diện nguồn REST

Khi một nhân tố người sử dụng thực hiện một truy vấn, một URI được vượt qua đến máy chủ nguồn xác định sưu tập đích hoặc tài nguyên bộ phận của truy vấn. Khi một thành phần máy chủ đại diện cho máy chủ nguồn nhận được một truy vấn, nó trích lục URI từ truy vấn và thực hiện bất kỳ phép toán nào cần để thực hiện truy vấn. Các truy vấn mà nhằm đến các tài nguyên bộ phận tạo, đọc, sửa đổi, hoặc xoá bỏ trạng thái đại diện cho tài nguyên đó, và sau đó trả về kết quả trạng thái đại diện của tài nguyên, nếu thích hợp.

Tạo các thành phần để xử lý các truy vấn HTTP

Để tạo và truy cập các thành phần cần thiết để thực hiện truy vấn HTTP từ một nhân tố người sử dụng đến một máy chủ nguồn và trở về, hoàn tất các bước sau đây:

  1. Tạo một đầu nối máy chủ, và sau đó bổ sung vào nó thành phần máy chủ HTTP phù hợp.
  2. Thêm vào hoặc truy cập ngữ cảnh HTTP đối với một ngữ cảnh máy chủ cho trước.
  3. Gửi truy vấn nhân tố người sử dụng đến mà thành phần máy chủ nhận được đến dịch vụ kinh doanh đích.
  4. Tạo và bổ sung bất cứ mô hình dữ liệu nào thể hiện tài nguyên do dịch vụ kinh doanh tạo và bao gói nó như là trạng thái đại diện của tài nguyên đích.
  5. Vượt qua trạng thái đại diện của tài nguyên trở lại nhân tố người sử dụng.

Hình 3 hiển thị các thủ tục này.

Hình 3. Điều khiển luồng khung làm việc REST
Điều khiển luồng khung làm việc REST

Mở rộng lớp DataHandler

Khung làm việc Hoạt hoá JavaBean (JAF) cung cấp một thành phần — lớp javax.activation.DataHander— nó có thể đại diện một cách hiệu quả trạng thái tài nguyên bộ phận. Việc sử dụng thành phần DataHandler để thể hiện các tài nguyên cho phép các tài nguyên đồng nhất với các kiểu nội dung tùy ý sẽ được trình bày theo giao diện chung.

Lớp trừu tượng hiển thị trong Liệt kê 8 mở rộng lớp DataHandler để cung cấp một lớp cơ sở chung cho các đối tượng mà thể hiện các tài nguyên bộ phận.

Liệt kê 8. Lớp Đại diện trừu tượng
public abstract class Representation extends javax.activation.DataHandler
{
  public Representation(DataSource dataSource)
  {
    super(dataSource);
  }
}

Để phục vụ cho hướng dẫn này, cài đặt riêng của giao diện Đại diện (Representation), bao gói các cấp tài nguyên nguồn cấp Atom, được tạo và hiển thị trong Liệt kê 9.

Liệt kê 9. Lớp đại diện
public class FeedRepresentation extends Representation
{
  public static final String MEDIA_TYPE = "application/atom+xml";  
  public static final String CONTENT_TYPE = MEDIA_TYPE + "; charset=utf-8";
  
  public FeedRepresentation(File file)
  {
    super(new FeedDataSource(file, CONTENT_TYPE));
  }
  
  public String getContentType()
  {
    return CONTENT_TYPE;
  }
}

Trong Liệt kê 9, một nguồn cấp tài nguyên được bao kín trong lớp FeedRepresentation. Lớp này sử dụng lớp FeedDataSource để quản lí các phép toán vào/ra cho một nguồn tệp.

Định nghĩa các đặc tính và phương thức để cài đặt

JAF cũng cung cấp một giao diện —javax.activation.DataSource— cho phép định nghĩa các đặc tính và phương thức sẽ được thực hiện bởi các tài nguyên, thể hiện các dữ liệu sưu tập tùy ý. Giao diện này cung cấp các phương thức để bộc lộ kiểu nội dung của tài nguyên và các dòng đầu vào và đầu ra mà ảnh hưởng đến dữ liệu cho tài nguyên đó. Các đối tượng DataHandler sử dụng các đối tượng để thực hiện giao diện DataSource để truy cập một tài nguyên ẩn qua cùng một giao diện chung.

Mỗi cài đặt của FeedDataSource đều trông cậy vào một cơ chế lưu trữ tài nguyên mức thấp (thí dụ cơ sở dữ liệu, hệ thống tệp) để lưu lại và lấy ra dữ liệu nguồn cấp. Như Liệt kê 10 cho thấy, việc cài đặt FeedDataSource dựa vào việc lưu trữ hệ thống tệp.

Liệt kê 10. Lớp FeedDataSource dựa trên tệp
public class FeedDataSource
  implements DataSource
{
  private File file = null;
  private String contentType = "";
  
  public FeedDataSource(File file, String contentType)
  {
    this.file = file;
    this.contentType = contentType;
  }

  public String getContentType()
  {
    return contentType;
  }

  public InputStream getInputStream()
      throws IOException
  {
    return new FileInputStream(file);
  }

  public String getName()
  {
    return file.getName();
  }

  public OutputStream getOutputStream()
      throws IOException
  {
    return new FileOutputStream(file);
  }

}

Trong Liệt kê 10, một tài nguyên tệp được bao gói trong lớp FeedDataSource. Lớp này thực hiện các phép toán vào/ra đối với một tài nguyên tệp cho trước.

Cài đặt tiện ích FrontController

Bạn cài đặt một tiện ích FrontController để tương tác với các thành phần khác nhau, các đầu nối, và các lớp ngữ cảnh để gửi đi truy vấn đến từ các nhân tố người sử dụng. Sau đó tiện ích phản hồi về nhân tố người sử dụng với trạng thái đại diện cho các tài nguyên được tạo từ các dịch vụ kinh doanh, nếu phù hợp với truy vấn, mà tiện ích làm đại diện.

Liệt kê 11 cho một thí dụ về việc xử lý phương thức HttpServlet doGet() chuẩn. Ngữ cảnh mà truy vấn nhằm đến được trích xuất và sử dụng để lấy ra thành phần máy chủ mà dữ liệu truy vấn được gửi đến.

Liệt kê 11. Phương thức doGet() cho lớp tiện ích FrontController
  protected void doGet(HttpServletRequest request,
                       HttpServletResponse response)
    throws ServletException,
           IOException
  {
    HTTPConnector connector =
      (HTTPConnector)Connectors.getConnector(Protocol.HTTP);
    HTTPServerComponent serverComponent =
      (HTTPServerComponent)connector.addServer(request.getServerPort());

    String contextRootPath = this.getServletContext().getRealPath("/");
    String contextPath = request.getContextPath() + "/"
                         + this.getServletName();
    serverComponent.addContext(contextRootPath, contextPath);

    Representation representation = null;
    
    try
    {
      representation =
        serverComponent.getContext(request,
                                   this).handleGet(new HTTPRequest(request));
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ServletException("Error: " + e);
    }
    
    new HTTPResponse(response).write(representation.getContentType(),
                                     representation.getInputStream());
  }

Trong Liệt kê 11, phương thức doGet của lớp FrontController minh hoạ cách mà truy vấn được nhận và xử lý. Một đầu nối cho giao thức HTTP được lấy ra và sử dụng để bổ sung/lấy một thành phần máy chủ. Đối tượng ngữ cảnh tài nguyên đích được lấy ra từ thành phần máy chủ. Sau đó đối tượng ngữ cảnh tài nguyên được dùng để xử lý truy vấn GET. Một đối tượng đại diện Resource trả lại bởi đối tượng ngữ cảnh tài nguyên. Sau đó đối tượng đại diện được truyền trở lại người truy vấn bằng cách sử dụng lớp HTTPResponse.

Cài đặt logic nghiệp vụ

Bạn sử dụng một lớp BusinessService để cài đặt logic nghiệp vụ, cho phép trả về dữ liệu mô hình thể hiện các tài nguyên bộ phận của các sưu tập APP riêng. Các lớp BusinessService thể hiện logic tác động đến các sưu tập riêng và các đại diện tài nguyên với một cài đặt lớp javax.activation.DataHandler và giao diện javax.activation.DataSource phù hợp với sưu tập đó hoặc nguồn cấp Atom của tài nguyên bộ phận.

Liệt kê 12 đưa ra một thí dụ về một dịch vụ kinh doanh mà bao gói một sưu tập của các tài nguyên nguồn cấp Atom.

Liệt kê 12. Lớp dịch vụ nghiệp vụ dùng cho sưu tập
public class CollectionService
    implements BusinessService
{
  // todo: read RESOURCES_DIR from config or system property
  //
  private static final String RESOURCES_DIR = "resources";

  private String contextRootPath = "";

  private String contextPath = "";

  public CollectionService(String contextRootPath,
                           String contextPath)
  {
    this.contextRootPath = contextRootPath;
    this.contextPath = contextPath;
  }

  protected String getContextPath()
  {
    return contextPath;
  }

  protected String filePathFromRequest(Request httpReq)
  {
    return contextRootPath + RESOURCES_DIR
           + ((HTTPRequest) httpReq).getRequest().getPathInfo();
  }

  protected void writeToFile(Request request,
                             File file,
                             boolean append)
      throws IOException,
        FileNotFoundException
  {
    System.out.println("writeToFile called");

    InputStream inStream = ((HTTPRequest) request).getInputStream();
    byte[] dataBuf = new byte[4096];

    FileOutputStream outStream = new FileOutputStream(file, append);
    int bytesRead = 0;
    while ((bytesRead = inStream.read(dataBuf)) > 0)
    {
      System.out.println("Writing [" + bytesRead + " bytes]");
      outStream.write(dataBuf, 0, bytesRead);
    }

    outStream.flush();
    outStream.close();
  }

  public Representation create(Request request)
      throws ServiceExecutionException
  {
    System.out.println("CollectionService.create()");

    // todo: create feed from structured storage with DAO
    //
    String filePath = filePathFromRequest(request);
    File file = new File(filePath);

    try
    {
      boolean append = false;
      writeToFile(request, file, append);
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ServiceExecutionException(e);
    }

    FeedRepresentation representation = new FeedRepresentation(file);

    return representation;
  }

  public Representation read(Request request)
      throws ServiceExecutionException
  {
    System.out.println("CollectionService.read()");

    // todo: read feed from structured storage with DAO
    //
    String filePath = filePathFromRequest(request);
    File file = new File(filePath);
    if (file.exists() == false)
    {
      throw new ServiceExecutionException("Feed file ["
                                          + filePath + "] does not exist.");
    }

    FeedRepresentation representation = new FeedRepresentation(file);

    return representation;
  }

  public Representation update(Request request)
      throws ServiceExecutionException
  {
    System.out.println("CollectionService.update()");

    // todo: update feed from structured storage with DAO
    //
    String filePath = filePathFromRequest(request);
    File file = new File(filePath);
    FeedRepresentation representation = new FeedRepresentation(file);

    try
    {
      boolean append = (file.exists() ? true : false);
      writeToFile(request, file, append);
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ServiceExecutionException(e);
    }

    return representation;
  }

  public void delete(Request request)
      throws ServiceExecutionException
  {
    System.out.println("CollectionService.delete()");

    // todo: delete feed from structured storage with DAO
    //
    String filePath = filePathFromRequest(request);
    System.out.println("CollectionService.delete() - resolving feed file ["
                       + filePath + "]");
    File file = new File(filePath);
    System.out.println("CollectionService.delete() - feed file resolved");
    if (file.exists())
    {
      System.out.println("CollectionService.delete() - deleting feed file");

      if (file.delete() == false)
      {
        System.out.println("CollectionService.delete() - feed deletion failed");
        throw new ServiceExecutionException("Error deleting feed ["
                                            + filePath + "]");
      }
      System.out.println("CollectionService.delete() - feed deletion succeeded");
    }
    else
    {
      System.out.println("CollectionService.delete() - feed file ["
                         + filePath + "] does not exist");
    }
  }
}

Liệt kê 12 cung cấp một thí dụ về một lớp kinh doanh dịch vụ mà bao gói một sưu tập của các tài nguyên nguồn cấp Atom. Lớp này sử dụng các tệp vật lý làm nguồn của mỗi tài nguyên nguồn cấp. Các phép toán trên mỗi tài nguyên nguồn cấp được định nghĩa bởi phương thức “CRUD” (tạo, đọc, cập nhật, và xoá). Trích xuất chung này của các phép toán cho phép nguồn vật chất mức thấp thay đổi mà không ảnh hưởng đến các ký hiệu phương thức hoặc giao diện dịch vụ.

Với các dịch vụ kinh doanh thể hiện các sưu tập của các tài nguyên nguồn cấp riêng tại chỗ, các nhân tố người sử dụng bây giờ có thể gửi truy vấn đến FrontController của máy chủ nguồn, nó gửi đi truy vấn đến dịch vụ kinh doanh phù hợp. Dịch vụ kinh doanh thực hiện các logic nghiệp vụ cần thiết, tác động đến bất kỳ tài nguyên đích nào. Các tài nguyên đích được tạo, truy cập, cập nhật, và xoá bỏ khi logic này ra lệnh. Trạng thái đại diện cho các tài nguyên bộ phận đích được tạo từ truy vấn được vượt trở lại tiện ích FrontController, nó sau đó chuyển tiếp trở lại nhân tố người sử dụng, nếu được yêu cầu, dưới dạng một phản hồi HTTP.


Tóm tắt

Tóm tắt

REST là một kiểu kiến trúc phần mềm và sưu tập của các nguyên tắc để xây dựng nên các hệ thống phân tán bằng cách sử dụng các giao thức và chuẩn hiện hành để tác động đến các tài nguyên thể hiện dữ liệu hoặc dịch vụ. REST đã tiến triển (hoặc chuyển giao) để mô tả bất kỳ khung làm việc nào mà chuyển tải dữ liệu qua một giao thức chẳng hạn như HTTP mà không dựa vào các lớp ngữ nghĩa bổ sung hoặc quản lý phiên làm việc.

APP là một giao thức dựa trên HTTP, nó truy cập, xuất bản, và sửa đổi các tài nguyên thể hiện dữ liệu hoặc dịch vụ. APP được thiết kế quanh ý tưởng của việc sử dụng các phép toán chuẩn mà giao thức HTTP cung cấp (GET, PUT, POST, và DELETE) để tự xem xét nội quan các sưu tập của các tài nguyên bộ phận và để vận dụng các tài nguyên bộ phận thể hiện các thứ như các mục blog, hình ảnh, tài liệu, và mục lịch.

Trong hướng dẫn này, bạn đã học được các khái niệm cơ sở của REST và APP. Bạn cũng học được cách áp dụng REST trong một hệ thống doanh nghiệp để gửi và nhận các truy vấn và phản hồi HTTP qua một hệ thống dựa trên tiện ích Java bằng cách sử dụng APP để tạo điều kiện cho việc truy cập và vận dụng trạng thái đại diện của các vùng chứa và tài nguyên bộ phận của các tài nguyên bộ phận.


Tải về

Mô tảTênKích thước
Source code for this articlex-restatompp.zip27KB

Tài nguyên

Học tập

Lấy sản phẩm và công nghệ

  • Phần mềm thử nghiệm IBM: Lập dự án phát triển của bạn với phần mềm thử nghiệm có sẵn để tải về trực tiếp từ developerWorks.

Thảo luận

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Nguồn mở, Công nghệ Java, SOA và dịch vụ Web
ArticleID=457942
ArticleTitle=Viết các dịch vụ REST
publish-date=12202007