이번 글에서는 CXF와 스프링을 사용해 주문 처리 웹 서비스를 구축하고 개발한다. 이 웹 서비스는 고객에게 주문을 받아 처리하고, 유효한지 점검해 유일한 주문 ID를 반환한다. 이 글을 읽은 후에 웹 서비스를 구축하고, 개발하는 데 CXF의 개념과 특징을 적용할 수 있을 것이다.
아파치 CXF(Apache CXF)는 웹 서비스를 편리하게 구축하고, 개발하는 데 필요한 견고한 기반을 제공하는 오픈 소스 프레임워크다. 이를 이용해 성능과 확장성이 높은 서비스를 만들 수 있다. 만든 서비스는 JBoss, IBM® WebSphere®나 BEA WebLogic 톰캣과 같은 좀 더 진보된 서버 인프라뿐만 아니라 톰캣과 스프링 기반 경량급 컨테이너에 배포할 수도 있다.
프론트엔드 모델링(front-end modeling): CXF는 다양한 프론트엔드 API를 사용해 웹 서비스를 생성하도록 해주는 프론트엔드 모델링 개념을 제공한다. 이 API는 간단히 팩토리 빈(factory beans)을 사용하고, JAX-WAS 구현을 통해 웹 서비스를 생성하도록 해준다. 또한 동적 웹 서비스 클라이언트도 생성할 수 있게 해준다.
도구 지원: CXF는 자바 빈, 웹 서비스, WSDL 간에 변환을 해주는 다양한 도구를 제공한다. 메이븐(Maven)과 앤트(Ant)에 대한 통합 지원 기능을 제공하며, 스프링과의 자연스러운(seamlessly) 통합도 지원한다.
RESTful 서비스 지원: CXF는 RESTful(Representational State Transfer) 개념을 지원하며, 자바 플랫폼에 대한 JAX-RS 구현을 지원한다(이번 연재의 Part 2에서 RESTful 서비스에 대한 더 많은 정보를 제공하겠다).
다양한 전송과 바인딩(binding) 지원: CXF는 XML에서 CSV(Comma Separated Value)에 이르기까지 여러 종류의 전송을 지원한다. 또한 JAXB(Java Architecture for XML Binding)와 SOAP과 HTTP 프로토콜 바인딩 외에 AEGIS 데이터 바인딩도 지원한다.
XML 이외의 바인딩 지원: CXF는 JSON(JavaScript Object Notation)과 CORBA(Common Object Request Broker Architecture) 같은 XML 이외의 바인딩을 지원한다. JBI(Java Business Integration) 아키텍처와 SCA(Service Component Architecture) 또한 지원한다.
JAX-WS 프론트엔드를 사용해 주문 처리 웹 서비스를 만들어 이를 스프링 빈(bean)으로 등록하는 방법을 확실하게 살펴보자. 자바 클래스를 먼저 개발하고, 이 클래스에 웹 서비스를 의미하는 애노테이션을 붙이는 코드 우선 접근법을 사용한다. 이를 위해서는 대체로 다음 단계를 따르면 된다.
서비스 종단 인터페이스(SEI: Service Endpoint Interface)를 만들고 웹 서비스로 노출되는 메서드를 정의한다.
구현 클래스를 만들고 웹 서비스 애노테이션을 붙인다.
beans.xml을 만들고 JAX-WS 프론트엔드를 사용해 스프링 빈으로 서비스 클래스를 정의한다.
Listing 1에서 볼 수 있는 것처럼, OrderProcess SEI는 웹 서비스 애노테이션이 붙은 단순한 표준 자바 인터페이스다. @WebService 애노테이션은 간단하게 인터페이스를 웹 서비스 인터페이스로 만들어준다. OrderProcess SEI는 하나의 processOrder 메서드를 갖는다. 이 메서드는 매개변수로 Order를 받아 문자열로 주문 ID를 반환한다.
이전 절에서 SEI 구현을 작성하면서 구현 클래스인 OrderProcessImpl이 웹 서비스가 되도록 애노테이션을 더해 붙였고, 이전 단계에서 만들었던 SEI의 완전한 클래스 이름(fully qualified name)을 값으로 갖는 endpointInterface 속성을 제공했다. 이 값은 이 클래스가OrderProcess SEI를 구현했다는 것을 의미한다. SEI를 구현했으므로 주문 ID를 반환하는 processOrder 메서드 구현을 제공해야 한다.
우리는 SEI와 SEI의 구현체를 만들었다. 이제 CXF를 사용해 JAX-WS 프론트엔드를 사용하는 실제 서비스 컴포넌트를 만들 수 있다.
CXF 환경 구성 파일은 실제로 빈 정의를 포함하는 스프링 환경 구성 파일이다. JAX-WS 프론트엔드 환경 구성을 사용해 OrderProcess웹 서비스에 대한 빈 정의를 만들자. beans.xml에 나오는 <jaxws:endpoint> 태그는 OrderProcess 웹 서비스를 JAX-WS 엔드포인트로 지정해준다. 이 웹 서비스를 공개(publish)하는 데 CXF가 내부적으로 JAX-WS를 사용하는 건 매우 효과적인 방법이다.<jaxws:endpoint>에 구현 클래스 이름인 OrderProcessImpl과 주소를 제공해야 한다. 여기서 제공하는 주소는 웹 컨텍스트(web context)와 관련 있다.
Listing 5에서 봤던 것처럼, 서버 종단 지점을 쉽게 만들 수 있는 것처럼 클라이언트 빈을 만드는 것도 매우 쉽다. JaxWsProxyFactory는OrderProcess 웹 서비스에 대한 클라이언트 빈을 만드는데 사용된다. 팩토리 빈은 서비스 클래스(OrderProcess)와 서비스의 URL을 요구한다. 그러므로 클라이언트 빈 스텁(stub)이 되는 OrderProcess는 팩토리 빈 참조를 사용해 생성된다.
public final class Client {
public Client() {
}
public static void main(String args[]) throws Exception {
ClassPathXmlApplicationContext context
= new ClassPathXmlApplicationContext(new String[]
{"demo/order/client/client-beans.xml"});
OrderProcess client = (OrderProcess)context.getBean("client");
Order order = new Order();
String orderID = client.processOrder(order);
System.out.println("Order ID: " + orderID);
System.exit(0);
}
}
OrderProcess 웹 서비스와 클라이언트를 구축하고, 배포하고, 실행하는 데 앤트(Ant) 도구를 사용한다. 코드는 톰캣 서버에 배포된다. c:\orderapp 폴더 아래 ant deploy 커맨드를 사용해 코드를 배포하자.
애플리케이션 폴더(c:\orderapp)는 앤트 빌드 파일을 갖고 있다. 위 명령을 실행한 후에 orderapp 코드는 orderapp.war 파일로 톰캣 서버 환경에 배포된다. 이제 CATALINA_HOME\bin 폴더에서 catalina start 명령을 입력해 톰캣 웹 서버를 시작하자.
톰캣의 webapps 폴더에 orderapp 폴더가 생성된다. 서버가 시작된 후에 ant client 명령을 입력해 애플리케이션을 시작하자. 주문 ID가 화면에 출력된다(그림 2).
이번 글에서는 간단한 CXF 프레임워크의 특징과 코드를 작성해야 하는 별다른 노력 없이도 웹 서비스를 만들 수 있는 방법을 설명했다. 빈 컨텍스트 파일을 사용하는 CXF와 스프링 통합에 대해서도 배웠다. 또한 프레임워크가 실제로 웹 서비스 인프라스트럭처 컴포넌트를 만드는 의미를 추상화한 방법과, 오로지 웹 서비스를 생성하는 데만 집중할 수 있도록 어떤 전문적이면서 간편한 셸 API를 제공하는지를 알아 봤다
이제 CXF를 사용하는 웹 서비스 생성의 기본을 살펴봤으니, Part 2에서는 CXF와 스프링을 사용해 RESTful 서비스를 POJO로 노출하는 방법을 살펴볼 생각이다.
Read examples of usage and a sample of the ant task report. If you have feedback, or conf you want to share with the world email me. If you have any suggestions/examples for this manual please post them to the group.
Install
Download the zip (or tar.gz) and extract it into your context's directory ie, so that urlrewrite.xml goes into the WEB-INF directory.
Add the following to your WEB-INF/web.xml (add it near the top above your servlet mappings (if you have any)): (see filter parameters for more options)
Add your own configuration to the WEB-INF/urlrewrite.xml that was created.
Restart the context.
You can visit http://127.0.0.1:8080/rewrite-status (or whatever the address of your local webapp and context) to see output (note: this page is only viewable from localhost).
Filter Parameters
There are a few advanced filter parameters for enabling conf file reloading etc. There are self-explanatory.
<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
<!-- set the amount of seconds the conf file will be checked for reload
can be a valid integer (0 denotes check every time,
-1 denotes no reload check, default -1) -->
<init-param>
<param-name>confReloadCheckInterval</param-name>
<param-value>60</param-value>
</init-param>
<!-- if you need to the conf file path can be changed
it is specified as a path relative to the root of your context
(default /WEB-INF/urlrewrite.xml) -->
<init-param>
<param-name>confPath</param-name>
<param-value>/WEB-INF/urlrewrite.xml</param-value>
</init-param>
<!-- sets up log level (will be logged to context log)
can be: TRACE, DEBUG, INFO (default), WARN, ERROR, FATAL, log4j, commons, slf4j,
sysout:{level} (ie, sysout:DEBUG)
if you are having trouble using normal levels use sysout:DEBUG
(default WARN) -->
<init-param>
<param-name>logLevel</param-name>
<param-value>DEBUG</param-value>
</init-param>
<!-- you can change status path so that it does not
conflict with your installed apps (note, defaults
to /rewrite-status) note, must start with / -->
<init-param>
<param-name>statusPath</param-name>
<param-value>/status</param-value>
</init-param>
<!-- you can disable status page if desired
can be: true, false (default true) -->
<init-param>
<param-name>statusEnabled</param-name>
<param-value>true</param-value>
</init-param>
<!-- you may want to allow more hosts to look at the status page
statusEnabledOnHosts is a comma delimited list of hosts, * can
be used as a wildcard (defaults to "localhost, local, 127.0.0.1") -->
<init-param>
<param-name>statusEnabledOnHosts</param-name>
<param-value>localhost, dev.*.myco.com, *.uat.mycom.com</param-value>
</init-param>
<!-- you may want to allow more hosts to look at the status page
statusEnabledOnHosts is a comma delimited list of hosts, * can
be used as a wildcard (defaults to "localhost, local, 127.0.0.1") -->
<init-param>
<param-name>statusEnabledOnHosts</param-name>
<param-value>localhost, dev.*.myco.com, *.uat.mycom.com</param-value>
</init-param>
<!-- defaults to false. use mod_rewrite style configuration file (if this is true and confPath
is not specified confPath will be set to /WEB-INF/.htaccess) -->
<init-param>
<param-name>modRewriteConf</param-name>
<param-value>false</param-value>
</init-param>
<!-- load mod_rewrite style configuration from this parameter's value.
note, Setting this parameter will mean that all other conf parameters are ignored.
<init-param>
<param-name>modRewriteConfText</param-name>
<param-value>
RewriteRule ^/~([^/]+)/?(.*) /u/$1/$2 [R]
RewriteRule ^/([uge])/([^/]+)$ /$1/$2/ [R]
</param-value>
</init-param>
-->
<!-- defaults to false. allow conf file to be set by calling /rewrite-status/?conf=/WEB-INF/urlrewrite2.xml
designed to be used for testing only
<init-param>
<param-name>allowConfSwapViaHttp</param-name>
<param-value>false</param-value>
</init-param>
-->
</filter>
<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
Note, setting logLevel to log4j or commons will cause the built in loging to call either log4j or commons-logging as if they were the logging framework, obviously you will need to have the jar for log4j or commons-logging in your classpath.
Configuration is done via a simple XML file that lives in your WEB-INF folder. It should be named urlrewrite.xml. It may be helpful to read the UrlRewriteFilter DTD (Document Type Definition). Please also make sure you look at the examples. A simple configuration file looks like:
The urlrewrite.xml file must have a root element called "urlrewrite" and must contain at least one "rule" element.
A "rule" must contain a "from" and a "to", and can have zero or more "condition" elements and zero or more and/or "set" elements.
When a "rule" is processed against an incoming request, all the "condition" elements must be met, then the "from" will be applied to the request URL and the final URL generated by applying the "to" to the "from" pattern. So long as the rule has matched then the "set" will be run.
When executing a rule the filter will (very simplified) loop over all rules and for each do something like this psuedo code:
Pattern.compile(<from> element);
pattern.matcher(request url);
matcher.replaceAll(<to> element);
if ( <condition> elements match && matcher.find() ) {
handle <set> elements (if any)
execute <run> elements (if any)
perform <to> element (if any)
}
<urlrewrite> element
The top level element.
Attribute
Possible Value
Explanation
default-match-type
(optional)
regex (default)
All rules and thier conditions will be processed using the Java Regular Expression engine (unless match-type is specified on a rule).
wildcard
All rules and thier conditions will be processed using the Wildcard Expression engine (unless match-type is specified on a rule).
decode-using
(optional)
header,utf8 (default)
When URL is decoded request.getCharacterEncoding() will be used, if that is empty UTF-8 will be used.
null
Do not decode at all. (note, this means the literal string null e.g. decode-using="null")
header
Only use request.getCharacterEncoding() to decode.
[encoding]
Only use a specific character encoding eg, ISO-8859-1. See Java Charset Object for all character encodings.
header,[encoding]
When URL is decoded request.getCharacterEncoding() will be used, if that is empty a specific character encoding eg, ISO-8859-1. See Java Charset Object for all character encodings.
use-query-string
(optional)
false (default)
The query string will not be appended to the url that the "from" element matches against.
true
The query string will be appended to the url that the "from" element matches against.
use-context
(optional)
false (default)
The context path will not be added to the url that the "from" element matches against.
true
The context path will be added to the url that the "from" element matches against.
<rule> element
Zero or more. The basis of a rule.
Attribute
Possible Value
Explanation
enabled
(optional)
true (default)
Enable this rule.
false
Disable this rule.
match-type
(optional)
regex (default)
This rule and it's conditions will be processed using the Java Regular Expression engine.
Using the example above JSP's with the code <a href="<%= response.encodeURL("/world.jsp?country=usa&city=nyc") %>">nyc</a>
will output <a href="/world/usa/nyc">nyc</a>
Or JSTL <a href="<c:url value="/world.jsp?country=${country}&city=${city}" />">nyc</a>
will output <a href="/world/usa/nyc">nyc</a>
Note, If you are using JSTL (ie, <c:url) this will work also.
<name> element
An optional element used for documenting the name of the rule. This can be used with rule and outbound-rule. See ant task.
A simple optional element used for documentation of the rule. This can be used with rule and outbound-rule. See ant task.
<rule>
<name>World Rule</name>
<note>
Cleanly redirect world requests to JSP,
a country and city must be specified.
</note>
<from>^/world/([a-z]+)/([a-z]+)$</from>
<to>/world.jsp</to>
</rule>
<condition> element
An element that lets you choose condtions for the rule. Note, all conditions must be met for the rule to be run (unless "next" is set to "or" obvoiusly).
If used, the header name must be specified in the "name" attribute.
method
The method of the request. GET, POST, HEAD etc.
port
The port that the web application server is running on.
time
Current time at the server (this will be the number of seconds since 00:00:00 1970-01-01 UTC otherwise known as unix time).
i.e. (new Date()).getTime()
This can be used for making sure content goes live only at a time you set.
year
Current year at the server.
i.e. (Calendar.getInstance()).get(Calendar.YEAR)
month
Month at the server. January is 0
i.e. (Calendar.getInstance()).get(Calendar.MONTH)
dayofmonth
Day of the month at the server. March first is 1
i.e. (Calendar.getInstance()).get(Calendar.DAY_OF_MONTH)
dayofweek
Day of the week at the server. Saturday is 1, Sunday is 7
i.e. (Calendar.getInstance()).get(Calendar.DAY_OF_WEEK)
ampm
AM or PM time at the server.
i.e. (Calendar.getInstance()).get(Calendar.AM_PM)
hourofday
The hour of the day (24 hour clock) at the server. 10pm is 22
i.e. (Calendar.getInstance()).get(Calendar.HOUR_OF_DAY)
minute
The minute field of the current time at the server.
i.e. (Calendar.getInstance()).get(Calendar.MINUTE)
second
The second field of the current time at the server.
i.e. (Calendar.getInstance()).get(Calendar.SECOND)
millisecond
The millisecond field of the current time at the server.
i.e. (Calendar.getInstance()).get(Calendar.MILLISECOND)
attribute
Will check the value of a request attribute (don't confuse this with parameter!), name must be set when using this type.
i.e. request.getAttribute([name])
auth-type
Will check the value of a request attribute (don't confuse this with parameter!)
i.e. request.getAuthType()
character-encoding
The character encoding of the imcoming request.
i.e. request.getCharacterEncoding()
content-length
The length of the imcoming request (can be useful if you want to deny large requests).
i.e. request.getContentLength()
content-type
The type of the imcoming request. (this is probably not that useful)
i.e. request.getContentType()
context-path
The context path of the imcoming request.
i.e. request.getContextPath()
cookie
The value of a cookie, note, name must be specified to use this
i.e. request.getCookies() the find we the one with [name] specified and check the value.
parameter
A tidier way of checking request parameters than looking for them in the query string. This will check for the parameter in GET or POST, note, name must be specified.
i.e. request.getParameter([name])
The protocol used to make the request, e.g. HTTP/1.1
i.e. request.getProtocol()
query-string
The query string used to make the request (if any), e.g. id=2345&name=bob
i.e. request.getQueryString()
remote-addr
The IP address of the host making the request, e.g. 123.123.123.12
i.e. request.getRemoteAddr()
remote-host
The host name of the host making the request, e.g. 123qw-dsl.att.com (note, this will only work if your app server is configured to lookup host names, most aren't).
i.e. request.getRemoteHost()
remote-user
The login of the user making this request, if the user has been authenticated, e.g. bobt
i.e. request.getRemoteUser()
requested-session-id
Returns the session ID specified by the client, e.g. 2344asd234sada4
i.e. request.getRequestedSessionId()
Returns the part of this request's URL from the protocol name up to the query string in the first line of the HTTP request
i.e. request.getRequestURI()
request-url
Reconstructs the URL the client used to make the request. The returned URL contains a protocol, server name, port number, and server path, but it does not include query string parameters.
i.e. request.getRequestURL()
You must always have exactly one from for each rule or outbound-rule. Value can be a regular expression in the Perl5 style. Note, from url's are relative to the context.
Attribute
Possible Value
Explanation
casesensitive
(optional)
false (default)
This value will be matched using case insentitive match. ie, "/WellingtoN" will match "/wellington".
true
This value will be matched using case sentitive match. ie, "/aAa" will NOT match "/aaa".
Example:
<from>^/world/([a-z]+)$</from>
<to> element
Value can be a regular replacement expression in the Perl5 style.
Attribute
Possible Value
Explanation
type
(optional)
forward (default)
Requests matching the "conditions" for this "rule", and the URL in the "from" element will be internally forwarded to the URL specified in the "to" element. Note: In this case the "to" URL must be in the same context as UrlRewriteFilter. This is the same as doing: RequestDispatcher rq = request.getRequestDispatcher([to value]);
rq.forward(request, response);
passthrough
Identical to "forward".
redirect
Requests matching the "conditions" and the "from" for this rule will be HTTP redirected. This is the same a doing: HttpServletResponse.sendRedirect([to value]))
permanent-redirect
The same as doing: response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
response.setHeader("Location", [to value]);
(note, SC_MOVED_PERMANENTLY is HTTP status code 301)
temporary-redirect
The same as doing: response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
response.setHeader("Location", [to value]);
(note, SC_MOVED_TEMPORARILY is HTTP status code 302)
pre-include
post-include
proxy
The request will be proxied to the full url specified. commons-http and commons-codec must both be in the classpath to use this feature.
last
(optional)
false (default)
The rest of the "rules" will be processed if this one succeeds.
true
No more "rules" will be processed if this one is a match.
encode
(optional)
false (default if under rule)
response.encodeURL([to]) will be run on the to url before performing the rewrite.
If your application server is configured to allow "cross context" communication then this attribute can be used to forward (and only forward, not redirect or other "to" types) requests to a named servlet context.
On Tomcat, for instance, the application contexts in the server configuration (server.xml or context.xml) need the option crossContext="true". For instance, the two applications mentioned before ("app" and "forum") have to be defined as:
Note, "to" can be null ie, <to>null</to>, this will mean that the request will go no further if the rule is matched (ie, this filter will not call chain.doFilter).
If "to" is set to -, no substitution will take place and the request will go on like nothing happened (ie, this filter will call chain.doFilter).
<to>/world.jsp?country=$1</to>
To elements can contain backreferences and variables.
Backreferences
%N
Provides access to the grouped parts (parentheses) of the pattern from the last matched Condition in the current rule. N must be less than 10 and greater than 0 (i.e. %1, %2, %3 etc).
Variables
%{VARIABLE-NAME}
Any valid condition type can be used as a variable name. ie, '%{port}' will be translated to '80', '%{year}' to '2005', '%{cookie:myCookie}' would be translated to 'myCookieValue' (assuming the user had a cookie named myCookie with the value myCookieValue).
Valid types are condition types, see condition for a full description.
Functions
${FUNCTION:PARAMS}
Functions can be places in set and to elements.
name
example
example returns
replace
${replace:my cat is a blue cat:cat:dog}
my dog is a blue dog
replaceFirst
${replaceFirst:my cat is a blue cat:cat:dog}
my cat is a blue dog
escape
${escape:a b c}
a+b+c
unescape
${unescape:a+b+c}
a b c
lower
${lower:Hello World}
hello world
upper
${upper:hello}
HELLO
trim
${trim: abc def }
abc def
<set> element
Allows you to set varous things if the rule is matched.
Attribute
Possible Value
Explanation
type
(optional)
request (default)
The same as request.setAttribute([name], [value]) (note, name must be set).
session
The same as request.getSesison(true).setAttribute([name], [value]) (note, name must be set).
response-header
The same as response.setHeader([name], [value]) (note, name must be set).
cookie
Value can be in the format "[value][:domain[:lifetime[:path]]]". This sets a cookie on the client's browser. The cookie's name is specified by the name attribute. The domain field is the domain of the cookie, such as '.apache.org',the optional lifetime is the lifetime of the cookie in seconds, and the optional path is the path of the cookie (note, name must be set).
Will set the Expires HTTP header by adding the time specified and current time (this is mod_expires style). Syntax "{num type}*". Units can be (singular or plural); years, months, weeks, days, hours, minutes, seconds.
eg, "1 day 2 seconds", "3 hours", "1 year 1 hour"
locale
The same as response.setLocale([value]) specify the Locale in the format (valid locales are, zh, zh-CN, zh-CN-southern i.e. "-" separating the language, country and variant (if any)).
parameter
Enables you to override a request.getParameter(String) wuth a custom value
method
Enables you to override request.getMethod() with a custom value
name
(optional)
(can be anything)
If type is request, session, response-header, cookie this specifies the name item.
In the following example a request attribute "client" will be set to "AvantGo" or "Samsung SCH-6100", this can be fetched in a servlet or JSP using request.getAttribute("client").
Allows you to run a method on an object when a rule and it's conditions are matched.
Attribute
Possible value
Explanation
class
The class you want to run a method on. Must be a fully qualified name.
method (optional)
run (default)
The method you want to run, the method must have the parameters (HttpServletRequest, HttpServletResponse) e.g. run(HttpServletRequest request, HttpServletResponse response)
Note, if init(ServletConfig) or destroy() is found they will be run at when creating or destroying an instance.
neweachtime (optional)
false (default)
One instance for each UrlRewriteFilter instance.
true
A new instance of the class will be created before running each time set to true.
When the rule in the following example is matched, WorldServlet.goGet(HttpServletRequest, HttpServletResponse) will be invoked, the request will then be forwarded to /world-presentation.jsp.
If the method being called throws an Exception the original exception will be re-thrown as if it were the original if it extends RuntimeException (eg, NullPointer), other exceptions are wrapped in a ServletException and thrown so your container can handle them.
<class-rule> element
Allows you to run a method every time a request come in for 100% dynamic rules. See the org.tuckey.web.filters.urlrewrite.sample package for an example.
Attribute
Explanation
class
The class you want to run a method on. Must be a fully qualified name.
method (optional, default matches)
The method you want to run, the method must have the parameters (HttpServletRequest, HttpServletResponse) e.g. run(HttpServletRequest request, HttpServletResponse response)
Note, if init(ServletConfig) or destroy() is found they will be run at when creating or destroying an instance.
last (optional, default true
If false more rules will be processed following this rule even if it is matched (so that a better match may be found).
Example:
<class-rule class="com.blah.web.MyRuleClass" />
Tips
When you want to put an "&" in a rule you must enter it as the XML entity "&"
For simplicity you might want to start all from's with a ^ and end them with a $.
In regular expressions ^ specifies the start of the string and $ specifies the end.
ie, a request for /my/url/path will NOT match <from>^/url/$</from> but it will match <from>/url/</from>
If using <outbound-rule> remember all urls in your code must be encoded e.g. <a href="<%= response.encodeURL("/some/olddir/b.jsp") %>">my link</a>
If you find regular expressions difficult use Wildcards.
"Context" is important. If you have an app with the context "/myapp" and you request the url "/myapp/somefolder/somepage.jsp", the container tells UrlRewriteFilter that the url is "/somefolder/somepage.jsp". This can be confusing, but basically your rules and conditions should not contain the context path (it will be handled by the container).
Wildcard Matching Engine
The wildcard matching engine can be used instead of regex. It is supported in conditions and rules where match-type is set to wildcard (or default-match-type is set on the urlrewrite element
e.g. /big/url/* will match /big/url/abc.html but will NOT match /big/url/abc/dir/ or /big/url/abc/.
/big/url/** will match /big/url/abc.html, /big/url/abc/dir/ and /big/url/abc/.
You can also use Regular expression style variable replacement, each match of a * will be available for use in to and set elements using simple $1$2 variables.
e.g. /my/big/url/* will match /my/big/url/abc.html and $1 will be set to abc.html.
Added in 3.0
Ant Task
An Ant task has been written to allow validate the conf file and generation of documentation. You can view a sample.
Paste the following into your build.xml file, then change the dest and conf to point to the correct places. Note, the urlrewrite jar file will need to be in your classpath.
<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
<!-- defaults to false. use mod_rewrite style configuration file (if this is true and confPath
is not specified confPath will be set to /WEB-INF/.htaccess) -->
<init-param>
<param-name>modRewriteConfText</param-name>
<param-value><![CDATA[
# redirect mozilla to another area
RewriteCond %{HTTP_USER_AGENT} ^Mozilla.*
RewriteRule ^/no-moz-here$ /homepage.max.html [L]
]]></param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
OR alternately set modRewriteConf to true in filter parameters and add a WEB-INF/.htaccess file with your mod_rewrite style configuration in it.
<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
<!-- defaults to false. use mod_rewrite style configuration file (if this is true and confPath
is not specified confPath will be set to /WEB-INF/.htaccess) -->
<init-param>
<param-name>modRewriteConf</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
Sample: WEB-INF/.htaccess
# redirect mozilla to another area
RewriteCond %{HTTP_USER_AGENT} ^Mozilla.*
RewriteRule ^/no-moz-here$ /homepage.max.html [L]
대칭암호화알고리즘이 무엇인지 간략히 소개하고, JCA-JCE및 간단한 두가지 알고리즘을 획득하여 암호화 하는 방법을 소개하고자 한다. ( 2003/06/23 ) 241
Written by ienvyou - 최지웅
1 of 1
오랫만이 쓰는 아티클인것 같다. 여기서는 우선 맛보기로 간단하게 암호화에 대한 내용 및
대칭암호방법에 대한 내용을 설명하도록 하겠다.
요즘 같이 인터넷이 보편화되고 개방형 시스템들이 존재하게 되면서 개인의 정보및
비밀에 대한 정보보호가 굉장한 이슈로 대두되어지고 있다.
우리가 사용하게 되는 자바는 java.security및 javax.crypto패키지를 통하여 암호를 쉽게
사용할 수 있는 기능을 제공한다.
만약 당신이 어떠한 데이터를 다른 사람에게 전달하던 도중 그 메시지를 다른 사람이 보게
된다면? 그게 만약 신용카드나 결제정보, 중요한 문서일수도 있는 것이다.
그러한 것들에 대한 침입위험으로부터 정보가 보호되어지기 위해서는 무언가의 수단이
필요하게 될것이다. 자바측에서도 코드정책에 대한 보안이 필요하겠지만 우선은
기본적인 사항만을 여기서 논하겠다.
기본적인 자바의 코드보안정책으로서 final class라든지, 패키지보안, jar파일 sign,
serialization부분의 transient등에 대한 설명은 여기서 논의하지 않도록 하겠다.
▶ 대칭암호화 알고리즘
기본적으로 이 글을 읽는 사람이라면 학교다닐때 한번쯤은 친한 친구와의 의사소통이나
대화를 하는데에 있어서 남들이 알지 못하는 방법으로 대화를 하고 싶었던 적이 있을것이다.
(놀새~만 그랬나?)
가령 내가 고등학교를 다닐때 친구와 이야기를 할때 모든 단어를 표현하고자 할때
중성을 뒤로 빼서 그것을 이용하여 한글자를 더 만들고 거기에 ㅅ받침을 이용하여 의사소통을
한적도 있었다. (예: 비둘기 --> 비시 두술 기시) 처럼말이다..
위의 예를 든것도 일종의 암호화의 한 방법이라고 할 수 있는데 일반 사람들이 듣기에는
어떠한 논리에 의하여 말을 하는 것인지 처음에는 알아듣지를 못하다가 단어를 적어주거나
한참후에야 그러한 원리가 있었음을 알게 된다.
위와 같이 암호화와 같은 정보를 이용하여 다시 복호화 할수 있는 데 좀 더 쉽게 예를 들어보자면
다음과 같을 수 있다.
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
D E F G H I J K L M N O P Q R S T U V W X Y Z A B C
각각의 문자를 3자리씩 뒤로 옮겨진 것 처럼 보여질 수 있는데 만약 HI, CAROUSER라는 단어는
KL, FDURXVIU 라는 단어로 바뀌게 되면 같은 패턴에 의하여 다시 복호화 되어질수 있는
특징을 가지고 있다.
즉 메시지를 암호화와 복호화를 하기 위해서는 알고리즘과 키가 정의되어야 한다.
▶ 대칭암호화 알고리즘의 종류
DES & TripleDES : DES(Data Encryption Standard)는 "Lucifer"라는 이름의 IBM에서 최초개발되었다.
또한 미국에서 최초 국가표준이 되었으며, 56비트키를 가지고 암복호화를 사용했다. 하지만
컴퓨터가 발전을 함에 있어서 56비트키의 경우 어느정도의 시간만 확보가 된다면 풀어낼수 있기
때문에 좀더 완벽한 보안을 위하여 Triple DES가 고안되어졌다.
TripleDES : 이는 기존의 DES암호화 알고리즘방식을 다른키에 세번 적용시킨것이며, 첫번째
암호화과정, 두번째 복호화과정, 세번째는 또 다른 암호화 과정을 거치도록 하고 있다.
그래서 이름이 DESede(DES encryption, decryption, encryption)이 되었으며, 각각의 과정에 따라
56비트의 배수로 암호화 복잡도가 증가되게 되어있다.
Blowfish : 1993년 Bruce Schneier에 의해 고안된 블록암호로서 DES보다 빠르고 안전한 기법을
제공한다. 최대 키의 비트수를 448비트까지 확장할수있다는 특징을 가지고 있다.
RC4 : "Civest's Code 4"를 의미하며 1987년 RSA Data Security에서 발표되었다. 보통 이기법으로
TCP/IP연결을 안전하게 하는 SSL을 구현하는 데 많이 쓰인다(키의 길이 40비트 또는 128비트)
위과 같이 대칭암호화의 경우는 모든 알고리즘이 같은 방식으로 동작을 하게 되는데
문제는 메시지를 보내고 받는 사람이 같은 키를 가지게 되며, 중간에 그 키가 노출이 되었을 땐
이미 우리의 메시지는 남에게 읽힐수 있는 문제점을 가지고 된다. 즉 비밀키가 노출되게 되면
암호화된 메시지또한 동시에 노출될 수 있다는 단점을 가지고 있다.
▶ DES방식의 샘플코드
우리는 여기서 간단하게 DES방식을 이용하여 암복호화 프로그램을 하나 예제로 보도록 한다.
기본적으로 자바측에서 제공할수 있는 암호에 관련된 패키지는 JCA(java cryptograhpy arch.)와
JCE(java cryptography extension)을 사용할 수 있다.
JCA는 기본적으로 Java2 Runtime Environment의 일부이며, JCA는 그것의 확장패키지이다.
기본적인 JCA에서는 전자서명, 메시지 다이제스트, 키생성기등의 클래스를 가지고 있으며
그러한 기본적인 클래스들을 우리가 사용하고자 했을 때 new에 의한 생성이 아니라
이미 아키텍쳐가 가지고 있는 암호화 기법에 의하여 factory형태의 클래스에게 생성을
요청하여야 한다.
기본적인JCE같은 경우 당신이 JDK1.3.1을 사용한다면 패키지를 썬에서 다운로드 받을수있으며
jdk1.4버젼을 사용한다면 이미 포함되어져 있으니 그냥 코딩을 해도 무방할 듯 하다.
우선 아래의 코드를 보기 전에 중요한 클래스를 살펴보아야 할 필요가 있는데 그것은
바로 javax.crypto.Cipher라는 클래스로서 데이터를 암호화하고 복호화하는데 사용되는
기본적인 엔진부분이다.
몇가지의 메소드만을 살펴보겠는데 우선
getIntance(), init(), update(), doFinal()로서 암호화 알고리즘의 생성, Cipher인스턴스의 초기화,
암복호화, 암호화된 배열을 획득을 하는 메소드들이다.
또한 javax.crypto.KeyGenerator클래스는 암호화와 복호화에 필요한 키를 생성해내는 데 쓰이며
getInstance(), init(), generateKey()의 메소드 정도면 Cipher에 필요한 키를 만들어낼 수 있다.
import java.security.*;
import javax.crypto.*;
public class SimpleExample {
public static void main(String [] args) throws Exception {
if( args.length != 1) {
System.out.println("Usage : java SimpleExample text ");
System.exit(1);
}
String text = args[0];
System.out.println("Generating a DESded (TripleDES) key...");
// Triple DES 생성
KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");
keyGenerator.init(168); // 키의 크기를 168비트로 초기화
Key key = keyGenerator.generateKey();
System.out.println("키생성이 완료되었음");
// Cipher를 생성, 사용할 키로 초기화
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte [] plainText = text.getBytes("UTF8");
System.out.println("Plain Text : ");
for (int i = 0; i < plainText.length ; i++) {
System.out.print(plainText[i] + " ");
}
// 암호화 시작
byte [] cipherText = cipher.doFinal(plainText);
// 암호문서 출력
System.out.println("\nCipher Text : ");
for (int i = 0; i < cipherText.length ; i++) {
System.out.print(cipherText[i] + " ");
}
//복호화 모드로서 다시 초기화
cipher.init(Cipher.DECRYPT_MODE, key);
//복호화 수행
byte [] decryptedText = cipher.doFinal(cipherText);
String output = new String(decryptedText, "UTF8");
System.out.println("\nDecrypted Text : " + output);
}
};
위의 코드로서 간단하게 살펴볼 수 있는데, 출력되는 생성되는 키에 따라 결정되므로
매번 다른 결과의 암호화된 문자열을 볼 수 있는 특징을 가지고 있다.
Blowfish의 대칭암호화 기법또한 같은 방법에 의하여 만들어낼 수 있는데
단순히 위의 코드상에서의 변화는 KeyGenerator에서 "Blowfish"와 Cipher에서
인스턴스를 얻어낼때 단순히 "Blowfish/ECB/PKCS5Padding"을 이용하여 처리하면
128비트의 키를 이용한 암복호화를 테스트해볼 수 있다.
▶ Conclusion
기본적으로 가장 기본적인 암호화 방법을 살펴보았으며 기본적인 대칭알고리즘의 개념이
무엇인지만 알고 있어도 놀새~가 여러분들을 보는 기준에서 별 무리가 없어 보일듯 하다.
다음에 쓸 것으로는 유닉스 패스워드의 형태로 많이 쓰이는 메시지 다이제스트방법을
간단한 자바코딩을 이용하여 처리해보겠다.
위의 경우 웹사이트를 직접 만들었을 경우 사용자들에 대한 user id, password를
데이터베이스에 직접저장하는 것이 아니라 universal key의 개념으로 변환하여
처리하는 샘플을 보도록 하겠다..
From the base Tomcat Authentication Sequence (using JAAS you can see the changes made by both JBoss and the IIS Connector. The important bit to note here is on the ISAPI Connector DLL Sequence. Due to the dll injecting a Principal into the request for Tomcat, all of Tomcat's regular authenticator valves fail to authorize the user. This is because, by default, they check to see if a Principal already exists in the session and return if it does.
The Tomcat IIS Authenticator Sequence shows how using the Tomcat IIS Authenticator Valve makes tomcat continue the authentication and authorization. Authentication is 'checked' by verifying that the NTLM provided Principal is not-null. Authorization is performed by passing the Principal to the Security Realm. The security realm can then populate the user's Principal with the Role's the user is granted base on the implementation of the Realm.
JBoss will now be able to use any LoginModule to populate the user's roles since it's security realm (registered in Tomcat) will now be called.
Recently I came across a small problem with setting up Oracle UCM 10gR3 on a MSSQL2005 instance.
Using the net.sourceforge.jtds.jdbc.Driver class.
The DBAs had setup a clustered SQL environment and had given me MSSQLDB0001\ARCHIVETR.
ARCHIVETR being the SQL instance which I needed to install the UCM to.
For future reference all you need to do is append ‘;instance=ARCHIVETR’ to the connection string like the following:
What's the best way to read a large XML document?
Of course you'd use a SAX parser or a pull parser.
What's the best way to write a large XML document?
Building a DOM structure to describe a large XML document
won't work because it won't fit in memory.
Even if it did, it's not a simple API to use.
There hasn't been a solution that is
simple and memory efficient until now.
Writing API for XML (WAX) is a free, open-source, library
for writing XML documents.
I created it because I got an OutOfMemoryError
while trying to output a large XML document
from an application I wrote using JDOM,
another Java-based XML library.
I searched for other libraries
that could write large XML documents
but couldn't find any that were
as simple to use as I thought they should be.
WAX is released under the
LGPL
with the intention of making its use unencumbered.
It is well-tested and ready for production use.
The WAX home page is at
http://www.ociweb.com/wax/.
Java and Ruby versions are available now.
The Java version of WAX can be downloaded from Google Code at
http://code.google.com/p/waxy/.
For information about the Ruby version, click here.
Ports for other programming languages will follow.
WAX has the following characteristics:
focuses on writing XML, not reading it
requires less code than other approaches
uses less memory than other approaches
(because it outputs XML as each method is called rather than
storing it in a DOM-like structure and outputting it later)
doesn't depend on any Java classes
other than standard JDK classes
is a small library (around 16K)
writes all XML node types
always outputs well-formed XML or throws an exception
unless running in "trust me" mode
provides extensive error checking
automatically escapes special characters in
text and attribute values (unless "unescaped" methods are used)
allows most error checking to be turned off for performance
knows how to associate DTDs, XML Schemas and
XSLT stylesheets with the XML it outputs
is well-suited for writing XML request and response messages
for REST-based and SOAP-based services
This section provides many examples of using WAX.
Each code snippet is followed by the output it produces.
When the no-arg WAX constructor is used,
XML is written to standard output.
There are also WAX constructors that take
a java.io.OutputStream
or a java.io.Writer object.
Here's a simple example where only a root element is written:
WAX wax = new WAX();
wax.start("car").close();
<car/>
After a WAX object is closed,
a new one must be created in order to write more XML.
In the examples that follow, assume that has been done.
Let's write a root element with some text inside:
wax.start("car").text("Prius").end().close();
<car>Prius</car>
The default indentation used is two spaces.
The end method terminates the element
that is started by the start method.
In this case it's not necessary to call end
because the close method
terminates all unterminated elements.
Attributes must be specified before
any content for their element is specified.
For example, calling
start, attr and text is valid,
but calling
start, text and attr is not.
If this rule is violated then
an IllegalStateException is thrown.
Let's add an XML declaration:
WAX wax = new WAX(Version.V1_0); // Version is an enum
wax.start("car").attr("year", 2008)
.child("model", "Prius").close();
Like attributes, namespaces must be specified
before any content for their element is specified.
If this rule is violated then
an IllegalStateException is thrown.
<!DOCTYPE car SYSTEM "car.dtd">
<car year="2008">
<model>Prius</model>
</car>
Let's add and use entity definitions:
String url = "http://www.ociweb.com/xml/";
wax.entityDef("oci", "Object Computing, Inc.")
.externalEntityDef("moreData", url + "moreData.xml")
.start("root")
.unescapedText("The author works at &oci; in St. Louis, Missouri.",
true) // avoiding escaping for entity reference
.unescapedText("&moreData;", true)
.close();
<!DOCTYPE root [
<!ENTITY oci "Object Computing, Inc.">
<!ENTITY moreData SYSTEM "http://www.ociweb.com/xml/moreData.xml">
]>
<root>
The author works at &oci; in St. Louis, Missouri.
&moreData;
</root>
A common usage pattern is to pass a
WAX object to a method of model objects
that use it to write their XML representation.
For example, a Car class could have the following method.
Quickly create web services in Spring 2.5 using Apache CXF 2.0, a.k.a. XFire 2.0.
In this tutorial I explain how to get a web service
up and running using Spring 2.5 and Apache CXF 2.0, which is the
combination of Celtix and XFire
and is considered XFire 2.0. (I don't know what the Celtix team would say about that, but
that's what the XFire site says.) Here I just
treat the web service itself; to learn about consuming the web service using Spring and CXF,
please see the article Make Web Services
Transparent with Spring 2.5 and Apache CXF 2.0.
CXF supports both WSDL-first and Java-first web service development. This article takes
the Java-first approach.
Project Setup
The first thing you'll need to do is download
CXF from the Apache CXF site. At the time of this writing the project is in incubator status
and the latest version is 2.0.4. That's the version I'm using.
Also, the docs describe a "Hello, World" web service that just returns a string, and in this
tutorial we want to go a little further than that and actually do a class databinding.
Here are the service-side dependencies you'll need. Inside the distribution there is
a file called WHICH_JARS that describes in a little more detail what the JARs
are for and which ones you'll really need, if you're interested in that. But the following
is essentially the list given in the user docs.
CXF itself
cxf-2.0.4-incubator.jar
CXF dependencies
Note that for CXF 2.0.4 the documented dependencies are almost but not quite
the same as the JARs that are actually included in the distribution, once again,
at the time of this writing (February 2008). Specifically the stax,
neethi and XMLSchema JARs are not the ones listed. Here's
the corrected list for CXF 2.0.4:
In addition to the above, you will need to add jdom-1.0.jar since
Aegis databinding uses it.
Spring dependencies
In this case ignore what's in the CXF documentation, because we're integrating
with Spring 2.5 instead of Spring 2.0. I'm going to assume that you have Spring 2.5
already set up on your web services project, including Hibernate or anything else that
your web services will need on the implementation side.
Just in case you were wondering, you don't need the Spring MVC module's
DispatcherServlet as CXF comes with its own servlet,
org.apache.cxf.transport.servlet.CXFServlet.
OK, that should be good for basic project setup. Let's write a simple service.
You should be able to delete
${catalina.home}/work/Catalina/localhost/appName/SESSION.ser where
appName is your application (or just delete the whole work directory).
In Struts 2, resource bundles can be associated with classes. The
framework will automatically discover and load class-orientated
resource bundles. You can also specify one or more global resource
bundles, which would be available to all classes in the application,
using either the standard properties file, or a custom listener.
Properties file
Global resource bundles can be specified in the struts.properties configuration file.
struts.properties
struts.custom.i18n.resources=global-messages
The framework searches the class heirarchy first, then, as a last resource, checks the global resources.
Multiple resource bundles can be specified by providing a comma-separated list.
Aside from the properties file, a Listener could also be used to load global resource bundles.
ActionGlobalMessagesListener.java
public class ActionGlobalMessagesListener implements ServletContextListener { privatestatic Logger log = Logger.getLogger(ActionGlobalMessagesListener .class); privatestaticfinalString DEFAULT_RESOURCE = "global-messages";
/** * Uses the LocalizedTextUtil to load messages from the global message bundle. * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.Servle tContextEvent) */ public void contextInitialized(ServletContextEvent arg0) { log.info("Loading global messages from " + DEFAULT_RESOURCE); LocalizedTextUtil.addDefaultResourceBundle(DEFAULT_RESOURCE); log.info("Global messages loaded."); }
/** * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent) */ public void contextDestroyed(ServletContextEvent arg0) {
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
public static final String toString(byte[] ba, int offset, int length) {
char[] buf = new char[length * 2];
for (int i = 0, j = 0, k; i < length; ) {
k = ba[offset + i++];
buf[j++] = HEX_DIGITS[(k >>> 4) & 0x0F];
buf[j++] = HEX_DIGITS[ k & 0x0F];
}
return new String(buf);
}