Skip to content
This repository has been archived by the owner on Oct 6, 2022. It is now read-only.

Commit

Permalink
added some documentation and finally added decent support for matchin…
Browse files Browse the repository at this point in the history
…g the query part of URIs
  • Loading branch information
Delors committed Jun 7, 2012
1 parent 3b36dec commit 5875bb1
Show file tree
Hide file tree
Showing 33 changed files with 708 additions and 323 deletions.
21 changes: 13 additions & 8 deletions core/src/main/scala/org/dorest/server/DefaultResponseHeaders.scala
Expand Up @@ -20,8 +20,8 @@ package org.dorest.server
*
* @author Michael Eichberg
*/
class DefaultResponseHeaders(private var headers: Map[String, String] = Map())
extends ResponseHeaders {
class DefaultResponseHeaders(private var headers: Map[String, String] = Map()) extends ResponseHeaders {


def this(header: Tuple2[String, String]) {
this(Map() + header)
Expand All @@ -31,22 +31,27 @@ class DefaultResponseHeaders(private var headers: Map[String, String] = Map())
this(Map() ++ headers)
}

def set(key: String, value: String): Unit = {
headers = headers.updated(key, value)
def set(field: String, value: String): this.type = {
headers = headers.updated(field, value)
this
}

def foreach[U](f: ((String, String)) U) {
headers.foreach(f)
}

def apply(field: String): String = {
headers(field)
}

def get(field: String): Option[String] = {
headers.get(field)
}
}

object DefaultResponseHeaders {

def apply(headers: (String, String)*): DefaultResponseHeaders = {
val responseHeaders = new DefaultResponseHeaders(headers.toMap)
responseHeaders
}
def apply(headers: (String, String)*): DefaultResponseHeaders = new DefaultResponseHeaders(headers.toMap)

}

Expand Down
18 changes: 12 additions & 6 deletions core/src/main/scala/org/dorest/server/DoRestApp.scala
Expand Up @@ -18,20 +18,26 @@ package org.dorest.server
import collection.mutable.Buffer

/**
* Enables the registration of [[org.dorest.server.HandlerFactory]] objects.
* Enables the registration of [[org.dorest.server.HandlerFactory]] objects. When processing a request the server
* will will use/has to use the first HandlerFactory that returns a Handler object to process the request.
* The factories are/have to be tried in the order in which they are registered using the register method.
*
* This trait is to be implemented by DoRest servers.
* This trait is generally implemented by DoRest servers.
*
* @author Michael Eichberg
*/
trait DoRestApp {

private var _factories: Buffer[HandlerFactory] = Buffer()

def factories = _factories
/**
* The list of all registered ˚HandlerFactory˚ objects.
*/
protected[this] var factories = Buffer[HandlerFactory]()

/**
* Appends the given handler factory to the list of previously registered `HandlerFactory` objects.
*/
def register(handlerFactory: HandlerFactory) {
_factories += handlerFactory
factories += handlerFactory
}

}
4 changes: 2 additions & 2 deletions core/src/main/scala/org/dorest/server/DoRestServer.scala
Expand Up @@ -17,9 +17,9 @@ package org.dorest.server

/**
* Implements common functionality required when embedding DoRest.
*
*
* '''Remark''' This class is generally only relevant for developers who want to extend/embed DoRest.
*
*
* @author Michael Eichberg
* @author Mateusz Parzonka
*/
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/org/dorest/server/HTTPMethod.scala
Expand Up @@ -15,8 +15,8 @@
*/
package org.dorest.server

/** The list of all HTTP Methods as defined by "Hypertext Transfer Protocol -- HTTP/1.1 (RFC 2616)" and also
* "PATCH Method for HTTP (RFC 5789)".
/** The list of all HTTP Methods as defined by RFC 2616: "Hypertext Transfer Protocol -- HTTP/1.1" and also
* by RFC 5789: "PATCH Method for HTTP".
*
* @author Michael Eichberg
* @author Mateusz Parzonka
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/org/dorest/server/Handler.scala
Expand Up @@ -34,7 +34,7 @@ import java.io._
trait Handler {

/**
* The used protocol. E.g., HTTP/1.1
* The used protocol. E.g., HTTP/0.9, HTTP/1.0 or HTTP/1.1
*
* '''Control Flow''':
* This field will be set by the server before [[#processRequest(InputStream):Response]] is called.
Expand Down
23 changes: 18 additions & 5 deletions core/src/main/scala/org/dorest/server/HandlerFactory.scala
Expand Up @@ -18,17 +18,30 @@ package org.dorest.server
import java.lang.Long

/**
* HandlerCreators are responsible for matching URIs and – if the URI matches – to create a new Handler
* that will then handle the request.
* A HandlerFactory is responsible for matching URIs and – if an URI matches – to return a [[org.dorest.server.Handler]]
* that will then be used to handle the request and to create the response.
*
* '''Thread Safety'''
* Handler factories have to be thread safe. I.e., handler factories have to support the simultaneous
* matching of URIs; the DoRest framework use a single Handler factory for matching URIs.
* '''Thread Safety''': Handler factories have to be thread safe. I.e., handler factories have to support the
* simultaneous matching of URIs; the DoRest framework use a single Handler factory for matching URIs. However,
* a Handler object has to be thread safe if and only if the same handler object is returned more than once.
* Hence, to avoid/limit concurrency issues it is recommended to return a new Handler whenever a path matches.
*
* @author Michael Eichberg
*/
trait HandlerFactory {

/**
* Tries to match the path and query part of a given URI.
*
* '''Control Flow''': This method is called by the DoRest framework when an HTTP request is made.
* For example, imagine an HTTP request with the following
* URI is made: `http://www.opal-project.de/pages?search=DoRest`. In this case DoRest will analyze the URI
* and split it up. The path part would be `/pages` and the query string would be `search=DoRest`.
*
* @param path A URI's path part.
* @param query A URI's query part.
* @return `Some` handler if the path and query were successfully matched. `None` otherwise.
*/
def matchURI(path: String, query: String): Option[Handler]

}
Expand Down
8 changes: 5 additions & 3 deletions core/src/main/scala/org/dorest/server/NotAcceptable.scala
Expand Up @@ -17,16 +17,18 @@
package org.dorest.server

/**
* '''From the HTTP Spec.''':{{{
* '''From the HTTP Spec.''':
* <blockquote>
* Note: HTTP/1.1 servers are allowed to return responses which are
* not acceptable according to the accept headers sent in the
* request. In some cases, this may even be preferable to sending a
* 406 response. User agents are encouraged to inspect the headers of
* an incoming response to determine if it is acceptable.
* }}}
* </blockquote>
*
* @author Michael Eichberg
*/
object NotAcceptableResponse extends PlainResponse(406)
object NotAcceptableResponse extends PlainResponse(406)



Expand Down
32 changes: 15 additions & 17 deletions core/src/main/scala/org/dorest/server/OkResponse.scala
Expand Up @@ -15,32 +15,30 @@
*/
package org.dorest.server


/**
* Encapsulates an OK response.
*
* From RFC 2616 (HTTP/1.1):
*
* The request has succeeded. The information returned with the response is dependent on the method used in the request, for example:
*
* GET an entity corresponding to the requested resource is sent in the response;
*
* HEAD the entity-header fields corresponding to the requested resource are sent in the response without any message-body;
*
* POST an entity describing or containing the result of the action;
*
* TRACE an entity containing the request message as received by the end server.
*
* For further details see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">Hypertext Transfer Protocol -- HTTP/1.1 (RFC 2616)</a>
*
* '''RFC 2616 (HTTP/1.1)''':
* The request has succeeded. The information returned with the response is dependent on the method used in
* the request, for example:
*
* - GET an entity corresponding to the requested resource is sent in the response;
*
* - HEAD the entity-header fields corresponding to the requested resource are sent in the response without any message-body;
*
* - POST an entity describing or containing the result of the action;
*
* - TRACE an entity containing the request message as received by the end server.
*
* @see [[http://www.w3.org/Protocols/rfc2616/rfc2616.html RFC 2616 HTTP/1.1]]
*
* @author Michael Eichberg
*/
abstract class OkResponse extends Response {

final def code = 200 //OK

}

}

object OkResponse {

Expand Down
10 changes: 5 additions & 5 deletions core/src/main/scala/org/dorest/server/PlainResponse.scala
Expand Up @@ -15,15 +15,15 @@
*/
package org.dorest.server


/**
* @author Michael Eichberg
*/
abstract class PlainResponse(val code: Int) extends Response {

def headers = new DefaultResponseHeaders()
abstract class PlainResponse(
val code: Int,
val headers: ResponseHeaders = new DefaultResponseHeaders())
extends Response {

def body = None
final def body = None
}


Expand Down
25 changes: 5 additions & 20 deletions core/src/main/scala/org/dorest/server/Redirect.scala
Expand Up @@ -28,44 +28,29 @@ import java.io._
*/
class Redirect(val location: String) extends Handler {

val response = new Response {
def code = 303;

val headers = new DefaultResponseHeaders("Location" -> location)

def body = None
}
val response = new PlainResponse(303) { headers.set("Location", location) }

def processRequest(requestBody: InputStream) = {
// we actually don't process the request at all
response;
}

}

abstract class DynamicRedirect extends Handler {

def location : Option[String]
def location: Option[String]

private def response(): Response = {
location match {
case Some(l)
return new Response {
def code = 303;

val headers = new DefaultResponseHeaders("Location" -> l)

def body = None
}
case None NotFoundResponse
case Some(l) new PlainResponse(303) { headers.set("Location", l) }
case None NotFoundResponse
}
}

def processRequest(requestBody: => InputStream) : Response = {
def processRequest(requestBody: InputStream): Response = {
// we actually don't process the request at all
response();
}

}


Expand Down
66 changes: 43 additions & 23 deletions core/src/main/scala/org/dorest/server/Response.scala
Expand Up @@ -15,43 +15,63 @@
*/
package org.dorest.server

/** A response object encapsulates a request's response.
*
* The precise structure of a response depends on the request and is defined by
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">RFC 2616</a>.
*
* @author Michael Eichberg
*/
/**
* A response object encapsulates a request's response.
*
* The precise/expected structure of a response depends on the request and is defined by
* [[http://www.w3.org/Protocols/rfc2616/rfc2616.html RFC 2616]].
*
* @author Michael Eichberg
*/
trait Response {

/** The status code of the response.
*
* Go to: <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10">HTTP Status Codes</a> for further
* details.
*/
/**
* The status code of the response.
*
* @see [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 HTTP Status Codes]]
*/
def code: Int

/** A response's headers.
*
* The response headers for the Content-type and Content-length are automatically set based on the
* response body.
*
* '''Remark''': ResponseHeaders must not be null and mutable.
*/
/**
* A response's headers.
*
* @note The HTTP headers Content-Type and Content-Length are automatically set based on the
* response body.
* @return This response's header fields (non-null).
*/
def headers: ResponseHeaders

/** The body that is send back to the client.
*/
/**
* The body that is send back to the client.
*/
def body: Option[ResponseBody]
}

/**
* Factory object for creating a [[org.dorest.server.Response]] object.
*/
object Response {

def apply(responseCode: Int, responseHeaders: ResponseHeaders, responseBody: Option[ResponseBody]) =
/**
* Creates a new, generic [[org.dorest.server.Response]] object.
*
* @param responseCode A valid HTTP response code. See also [[org.dorest.server.Response]].code
* @param responseHeaders The HTTP response headers. See also [[org.dorest.server.Response]].headers
* @param responseBody The body of the HTTP response. See also [[org.dorest.server.Response]].body
* @return A new response object.
*/
def apply(responseCode: Int, responseHeaders: ResponseHeaders, responseBody: Option[ResponseBody]) = {
require(responseHeaders ne null)
require(responseBody match {
case Some(_) true // the body may be lazily initialized; hence we do not check that `body.length > 0`
case None true
case null false
});

new Response {
val code: Int = responseCode
val headers: ResponseHeaders = responseHeaders
val body: Option[ResponseBody] = responseBody
}
}

}
6 changes: 4 additions & 2 deletions core/src/main/scala/org/dorest/server/ResponseBody.scala
Expand Up @@ -18,7 +18,6 @@ package org.dorest.server
import java.nio.charset.Charset
import java.io.OutputStream


/**
* Encapsulates a response's body.
*
Expand All @@ -37,8 +36,11 @@ trait ResponseBody {
def length: Int

/**
* Called by the framework – after sending the HTTP header – to write
* Called by the DoRest framework – after sending the HTTP header – to write
* out the specific representation as the response's body.
*
* '''Contract'''
* Exactly as many bytes have to be written as specified by length.
*/
def write(responseBody: OutputStream): Unit
}
Expand Down

0 comments on commit 5875bb1

Please sign in to comment.