Representational State Transfer, also known as REST, is a style of software architecture defined by a collection of architectural principles. REST is applied specifically to such distributed systems as the Web, and stipulates mechanisms for defining and accessing resources. The term, originally described in a dissertation by Roy Fielding, often is used to describe any framework that transmits data over a protocol, such as HTTP, without additional semantic layers or session management.
The goal of REST is to simplify the implementation of actors in a client/server system by maintaining a strict separation of concerns. REST also seeks to simplify communication semantics, thereby improving performance and increasing the scalability of server components. REST relies on stateless interaction between requests, in which various media types are exchanged using standard message verbs.
REST can be implemented for the HTTP protocol using the Java programming language and JAF. In this article I introduce the basics of REST and JAF, then walk you through a REST implementation using JAF.
Note that Sun provides binaries of a JAF reference implementation that can be used to develop JAF components and clients on any Java 2 Standard Edition platform Version 1.4 or later.
REST for beginners
In a REST implementation, resources, such as items in a shopping cart, are addressed via uniform resource identifiers (URI). A given URI is used to access the representational state of a resource, and also to modify that resource. For example, let's say a widget manufacturer uses REST to make its widgets available for consumption. At any given time, the manufacturer can present Web URLs containing descriptive information about the widgets. Widget consumers then need to know only a widget's URL to read the information. If authorized, the consumer also could modify the information as needed. The following illustrates a typical REST URI:
http://www.example.com/widgets/foobar/E4456SE321DAACF567
Notice how the URI defines the location of the "foobar" widget without stipulating an action to take. This is the goal of a URI in REST -- to represent a resource with a URI and to leave action semantics up to the underlying protocol.
One of the defining principles of REST is to exploit already existing technologies and Web protocols, in particular, HTTP. This makes REST simpler to use than other Web-based message processes, such as Simple Object Access Protocol (SOAP). A REST implementation requires no additional overhead to provide or consume information. According to Fielding, a REST-based conversation is stateless between requests, thereby supporting such subscription-based technologies as RSS, in which Web-site content is described and delivered to interested clients.
Entities of a REST implementation
REST is an abstraction of fundamental architectural entities within a distributed system. REST defines these entities as follows:
- Data elements: Resources, resource identifiers (URIs and URLs), and resource representations, such as HTML documents, images and XML documents.
- Components: Origin servers, gateways, proxies and user agents.
- Connectors: Clients, servers, caches, and so forth.
Figure 1 illustrates some of the entities of REST and how they might participate within a simple enterprise system.
Figure 1. REST in a simple enterprise system
In a system like the one above, connectors would be embodied as ports to enable communication among the components over each given protocol. Also note that any component can have multiple client and server connectors to handle communication between itself and other client or server components.
REST and HTTP
According to Fielding's dissertation, representational state for resources in an HTTP-based REST system should be accessed using the standard HTTP methods. What follows is a simple breakdown of these methods:
GET
is used to transfer the current representational state of a resource from a server to a client.PUT
is used to transfer the modified representational state of a resource from the client to the server.POST
is used to transfer the new representational state of a resource from the client to the server.DELETE
is used to transfer information needed to change a resource to a deleted representational state.
The four standard HTTP methods are supported by Java language and server technologies, such as servlets and JavaServer Pages (JSP). You can also use JAF to extend these technologies to support a REST-based system.
What is JAF?
JAF is a standard extension to the Java platform. It presents a framework of Java-based APIs and components in which information about data presented by Java objects or beans can be recognized easily and accessed.
JAF defines APIs that are used to register and discover data by content or Multi-purpose Internet Mail Extensions (MIME) type. Standard methods defined by JAF components then can be used to instantiate data handlers, on which commands and operations are performed to access and modify the data.
Simply put, JAF does the following:
- Lets arbitrary data for a Java object or bean be registered and discovered according to content or MIME type using consistent APIs.
- Presents standard methods for discovering the operations and commands that are available for data encapsulated by a Java object or bean.
- Presents data handlers supporting commands and methods to access and modify date encapsulated by a Java object or bean.
With JAF, developers use standard APIs to determine the content type of data for a given resource encapsulated by a Java object or bean. For example, an image resource in JAF might be identified by a content or MIME type of image/png
. A DataHandler
component representing the image then could be used to access and modify the content of the image using the same standard streaming operations that would be used to access and modify content from a component representing an XML document, that is, a component with a content type of application/atom+xml
.
Components of the JAF architecture
Figure 2 shows the primary components defined by the JAF architecture.
Figure 2. Primary components defined by the JAF architecture
As shown in Figure 2, the DataHandler
class provides a standard interface between JAF clients and resources.
The DataSource
component encapsulates data for a resource by providing stream access to the data and by providing the content or MIME type describing the data. JAF provides two DataSource
implementations: FileDataSource
, representing file-system data, and URLDataSource
, representing data located by a given URL.
DataContentHandler
components can be implemented to provide cut-and-paste capabilities to a given DataHandler
via the java.awt.datatransfer.Transferable
interface.
The CommandMap
object represents a registry of command/verbs available for specific MIME types supported by a resource. The CommandMap
registry lets a DataHandler
component retrieve JavaBeans, which implement the CommandObject interface
, to operate on a representation of a resource supporting a particular MIME type.
Once a DataHandler
for a given resource is instantiated, a DataSource
object is created and associated with the DataHandler
. The DataSource
object provides content-type information to the DataHandler
component. A CommandMap
object can be associated with the DataHandler
component if the developer so chooses. The CommandMap
object is used to associate a collection of verb/JavaBean pairs with a DataHandler
. The verb/JavaBean pairs are used by a DataHandler
to present components to clients for handling customized commands for a given resource.
Resources and data elements in REST
Most distributed systems offer an architect only three options for exposing the data of a resource:
- Render the data where it is located and send a fixed-format image to the recipient.
- Encapsulate the data with a rendering engine and send both to the recipient.
- Send the raw data to the recipient along with metadata that describes the data type, so the recipient can choose his or her own rendering engine.
According to Fielding, REST provides a hybrid of these three data-rendering options by "focusing on a shared understanding of data types with metadata, but limiting the scope of what is revealed to a standardized interface."
Data elements in REST are summarized as:
- Resources
- Resource identifiers
- Representations
- Representation metadata
- Resource metadata
- Control data
REST components act on a resource by using a URI-based representation to acquire a snapshot of the current state of the resource and transmitting that snapshot between components. A representation in REST is defined as a sequence of bytes, plus representation metadata describing those bytes. The format of a resource representation is defined by its MIME or media type. It is this key aspect of REST that makes JAF and the MIME-defined aspects of JAF such great facilitators of a REST data-element implementation.
Now that you've learned a bit about REST and JAF, we'll look at a sample Web application that tracks REST-based requests and responses through a simple Java servlet-based system.
Implementing REST with JAF
In the next sections I will develop a REST implementation using JAF. I'll start by modeling a simple system of components around the primary entities defined by REST, after which I'll construct a Java application using servlets and JAF. For now, consider the class diagram in Figure 3, which offers a high-level view of the Java classes and interfaces that make up the JAF-based REST implementation for the sample application.
Figure 3. Classes and interfaces of a JAF-based REST implementation
As the class diagram shows, a FrontController
servlet interacts with most of the primary REST entities, which are represented as Java interfaces. The following sections discuss each of the primary REST entities and the Java components that represent them.
Java components for a REST implementation
REST components are entities that are defined by the role they play in a given enterprise system. Typical components in a REST system are as follows:
-
User agents
- A user agent initiates requests to some form of server component. The user agent then handles the response from the server component. A user agent can be a Web browser, a mobile device or another type of Web-enabled client. Origin servers
- An origin server embodies the namespace for a given collection of resources. An origin server is the source of representations of the state of the resources it embodies. An origin server receives requests from user agents, performs the necessary actions on the targeted resources, and responds to the user agent with the appropriate representational state for the resource and the actions performed by the origin server. An origin server typically exposes resources using a hierarchical interface. Intermediary components
- Intermediary components forward requests and responses to and from user agents and/or server components. An intermediary component might perform some type of transformation on a request or response as needed by a given system or application, such as data conversion, authentication and authorization, or performance optimization. Proxies and gateways are typical intermediary components.
In Java, you might represent a component using a marker interface, like so:
public interface Component
{
}
A server component in Java can expose the properties and methods by which resource contexts are managed, as illustrated in Listing 1.
Listing 1. ServerComponent exposes properties and methods
public abstract class ServerComponent
implements Component
{
private java.util.HashMap contexts =
new java.util.HashMap();
abstract public Context addContext(String contextRootPath, String contextPath);
public java.util.Iterator getContextPaths()
{
return contexts.keySet().iterator();
}
public Context getContext(String contextPath)
{
return contexts.get(contextPath);
}
protected java.util.HashMap getContexts()
{
return contexts;
}
}
An HTTP-specific server component can expose HTTP-specific contexts, as shown in Listing 2.
Listing 2. ServerComponent exposes HTTP-specific contexts
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;
}
An HTTP-specific context implements the Context
interface, where each HTTP method is handled and resource representations are manipulated and accessed. Listing 3 shows a REST implementation representing the HTTP GET
method. Note that the details of business logic for a particular vertical domain are encapsulated within individual business services.
Listing 3. A REST implementation representing HTTP GET
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;
}
REST components use connectors to interact with each other.
Connectors in REST
A connector is a software entity exposing a generic interface that enables communication among REST components. A connector typically represents one endpoint or port of a network communication protocol. A connector can be a client connector or a server connector. User agents use client connectors to instigate requests to an origin server. Origin servers use server connectors to receive requests from user agents and to respond with representational state for targeted resources.
A Java implementation of a connector can be represented by the following interface:
public interface Connector
{
public ServerComponent addServer(int port);
}
A factory class can be used to manage protocol-specific connectors in Java. Each connector can be pooled if needed. Listing 4 is an example of a Connector
factory class.
Listing 4. A Connector factory class
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;
}
An HTTP-specific server Connector
implements the Connector
interface and exposes HTTP-specific server components for each given port, as illustrated here:
public ServerComponent addServer(int port)
{
return new HTTPServerComponent(port);
}
Connectors facilitate communication among client components and server components. Server components represent resources that can be implemented as discussed in the next section.
Resource representation
For each client request, a URI is passed that identifies the targeted resource for the request. Once a server component receives a request, it reads the URI, performs any necessary actions to carry out the request for a resource, and returns the resulting representational state of the resource.
The flow for instantiating necessary components and accessing each to handle a typical request from a client to server and back again could be as follows:
- Create a connector and add to it an HTTP server component.
- Add the context for a given resource.
- Dispatch incoming client requests received by a server component to the appropriate business service.
- Encapsulate the model returned from the business service as a resource representation.
- Respond to the client with the state of the representation.
Figure 4 illustrates this flow of control.
Figure 4. A RESTful client-server request-response cycle
JAF's javax.activation.DataHandler
class exposes a component that can embody representations of resources. Using the DataHandler
class lets you present multiple types of resources of differing content types with a standard interface.
Listing 5 shows an abstract class that extends the DataHandler
class to encapsulate a standard base class for objects representing a wide array of resources.
Listing 5. Representation encapsulates resource objects
public abstract class Representation extends javax.activation.DataHandler
{
public Representation(DataSource dataSource)
{
super(dataSource);
}
}
JAF's DataSource class
JAF's javax.activation.FileDataSource
class implements the javax.activation.DataSource
interface to expose a simple object representing a file resource.
Listing 6 shows a specific implementation of the Representation
interface that encapsulates file resources.
Listing 6. Using Representation to encapsulate file resources
public class FileRepresentation extends Representation
{
public FileRepresentation(File file)
{
super(new FileDataSource(file));
}
public FileDataSource getDataSource()
{
try
{
return this.getDataSource();
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
public File getFile()
{
try
{
return this.getDataSource().getFile();
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
}
The FileDataSource
class provides content-typing using a FileTypeMap
object. The FileTypeMap
class relies on a default, MIME-based map to determine content types. Customization of the default content types can be facilitated using the setFileTypeMap
method.
JAF content types for REST resources
Implementations of the FileTypeMap
class implement the abstract getContentType
methods to return the content type of a resource using whatever mechanism is appropriate for the resource. This might entail reading the file extension of a file resource or opening the file and reading its contents to determine the content type. The FileDataSource
class uses the default FileTypeMap
to determine the content type of file resources. The default FileTypeMap
is an instance of the MimetypesFileTypeMap
class.
The MimetypesFileTypeMap
class extends FileTypeMap
and determines the content type of files by mapping their file extensions. The default mechanism employed by the MimetypesFileTypeMap
to find the extension mapping is as follows:
- Use any entries programmatically added to the
MimetypesFileTypeMap
instance. - Look for a file named "mime.types" in the user's home directory.
- Look for a file named "mime.types" in the /lib directory.
- Look for a file named "mime.types" in the META-INF directory of the resource classpath.
- Look for a file named "mime.types.default" in the META-INF directory of the resource classpath.
- The file or resource named META-INF/mimetypes.default. This is usually provided by the activation.jar file installed with the JAF reference implementation.
Listing 7 is an example of the MIME-types mapping file.
Listing 7. A MIME-types mapping file
MIME Type File Extension
========================================
text/html html htm HTML HTM
text/plain txt text TXT TEXT
image/gif gif GIF
image/ief ief
image/jpeg jpeg jpg jpe JPG
image/tiff tiff tif
image/png png PNG
image/x-xwindowdump xwd
application/postscript ai eps ps
application/rtf rtf
application/x-tex tex
application/x-texinfo texinfo texi
application/x-troff t tr roff
audio/basic au
audio/midi midi mid
audio/x-aifc aifc
audio/x-aiff aif aiff
audio/x-mpeg mpeg mpg
audio/x-wav wav
video/mpeg mpeg mpg mpe
video/quicktime qt mov
video/x-msvideo avi
REST request and response handling
Once components, connectors, and resource representation classes and interfaces are implemented, a FrontController
servlet is provided to receive incoming client requests and respond to clients with the representation state of the resources that it manages.
Listing 8 shows a sample implementation of the standard doGet
method of the HttpServlet
class. In this example, doGet
handles a REST-based HTTP GET
method.
Listing 8. doGet handles a REST-based HTTP GET
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());
}
Services to handle REST-based business logic
A BusinessService
class can be employed to encapsulate resource representations for specific-domain business logic. The BusinessService
classes should embody the implementations for each resource representation using an implementation of the javax.activation.DataHandler
class and the javax.activation.DataSource
interface that is appropriate for the content of the model generated from the service's business logic.
An example of a simple business service that encapsulates file resources is shown in Listing 9.
Listing 9. A business service encapsulating file resources
public class SimpleBusinessService
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 SimpleBusinessService(String contextRootPath,
String contextPath)
{
this.contextRootPath = contextRootPath;
this.contextPath = 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("SimpleBusinessService.create()");
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);
}
FileRepresentation representation = new FileRepresentation(file);
return representation;
}
public Representation read(Request request)
throws ServiceExecutionException
{
System.out.println("SimpleBusinessService.read()");
String filePath = filePathFromRequest(request);
File file = new File(filePath);
if (file.exists() == false)
{
throw new ServiceExecutionException("File [" + filePath + "] does not exist.");
}
FileRepresentation representation = new FileRepresentation(file);
return representation;
}
public Representation update(Request request)
throws ServiceExecutionException
{
System.out.println("SimpleBusinessService.update()");
String filePath = filePathFromRequest(request);
File file = new File(filePath);
FileRepresentation representation = new FileRepresentation(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("SimpleBusinessService.delete()");
String filePath = filePathFromRequest(request);
System.out.println("SimpleBusinessService.delete() - resolving file ["
+ filePath
+ "]");
File file = new File(filePath);
System.out.println("SimpleBusinessService.delete() - file resolved");
if (file.exists())
{
System.out.println("SimpleBusinessService.delete() - deleting file");
if (file.delete() == false)
{
System.out.println("SimpleBusinessService.delete() - file deletion failed");
throw new ServiceExecutionException("Error deleting file ["
+ filePath + "]");
}
System.out.println("SimpleBusinessService.delete() - file deletion succeeded");
}
else
{
System.out.println("SimpleBusinessService.delete() - file ["
+ filePath
+ "] does not exist");
}
}
}
As previously mentioned, business services should represent the data models generated by their specific logic using implementations of the JAF classes and interfaces that are appropriate for the content of the models. This might entail database resources, dynamically generated resources, file system resources and others.
In conclusion
REST is a style of software architecture and a collection of architecture principles for distributed systems that stipulates mechanisms for defining and accessing resources. REST can be used to describe a framework that transmits data over a protocol, such as HTTP, without additional semantic layers or session management. REST defines a separation of concerns and stateless conversations that simplify the actors and communication semantics in a distributed system. Within REST, media types are exchanged using standard message verbs.
JAF is a standard extension to the Java platform that presents a framework of Java-based APIs and components in which resources represented by Java objects or beans are recognized and accessed. JAF defines APIs through which data is registered and discovered by content or MIME type. These mechanisms make JAF and the MIME-defined aspects of JAF great facilitators of a REST data-element implementation.
In this article I have introduced you to REST and JAF. Using a sample Web application that tracks requests and responses through a Java servlet-based system, I have shown how JAF facilitates the exchange of data in REST-based systems. See the Resources section to learn more about REST and JAF.
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/71047/viewspace-996761/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/71047/viewspace-996761/