Tagged: http
RESTful HTTP DELETE?
DELETE
method 의 응답으로 어떤 값을 사용해야 할까? 하고 갑자기 궁금하던 중, 같은 논점에 대해, 상당양의 검색결과를 얻었다.
일단 idempotence 에 대한 내용은 RFC 2616 / 9.1.2 Idempotent Methods 에서 알아볼 수 있다.
RFC 의 내용은 그리 논란거리가 아니지만 이게 REST(ful) 동네로 넘어오면서 의견이 분분해지는 것 같다.
debate
논점을 좀 간단하게 하자면, 204
만 써야 하는가 아니면 404/410
등도 같이 써야 한다는 것인데…
개인적으로 204
를 사용하는 것이 옳다고 본다. 4xx
을 써도 된다는 의견들이 잘못됐다는 것은 아니지만 4xx
을 사용해야 한는 경우는 그것보다 더 넓은 의미와 상황을 포함한다.
중복된 HTTP 헤더는 모든 경우에 가능한가?
references
rfc-2616
4.2 Message Headers 에 다음과 같이 씨부라려져 있다.
Multiple message-header fields with the same field-name MAY be present in a message if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)]. It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma. The order in which header fields with the same field-name are received is therefore significant to the interpretation of the combined field value, and thus a proxy MUST NOT change the order of these field values when a message is forwarded.
잘못된 해석을 한 번 하자면 다음과 같다.
그냥 됨.
간단하게 쓰면 될 일을 왜 저렇게 썼는지 곰곰히 살펴보도록 하자.
Multiple message-header fields with the same field-name MAY be present in a message
여기까지는 좋다. 근데..
if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)].
if and only if
와 그 뒤의 내용이 좀 껄쩍지근한 건 단지 느낌일 뿐인걸까?
It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma.
이 부분이 좀 중요한 것 같다. 역설적으로, 만약 다음과 같은 사용될 수 있다면,
My-Awesome-Header: value1 My-Awesome-Header: value2 My-Awesome-Header: value3
다음과 같이 콤마(‘,’)로 분리해서 한꺼번에 사용해도 동일한 의미와 순서를 가지는 헤더에 한해서, 위에 있는 것 처럼, 같은 이름으로 나눠서 사용할 수(MAY) 있다는 얘기다.
My-Awesome-Header: value1,value2,value3
Accept 헤더를 다음과 같이 사용하면
Accept: application/xml Accept: application/json
원래는 아래와 같이 사용해야 하는데 (그렇게 정의되어 있다.) 위처럼 사용할 수 있다는 의미다.
Accept: application/xml,application/json
하지만 Content-Type 를 다음과 같이 사용하면 안될 듯 하다.
PUT /items/1 HTTP/1.1 Host: whatever Content-Type: application/xml Content-Type: application/json ㅋㅋㅋ
다음과 같은 응답메시지를 볼 수 있을지도…
HTTP/1.1 400 씨발새꺄 장난하냐?
Adding additional headers on a ServletRequest
References
- Adding Header Information to an existing HTTP Request
- How to modify request headers in a J2EE web application
- Modifying HTTP headers using Java
Problem
Simply, we can’t add or modify request headers on given HttpServletRequest
instances.
Solution
The only solution, at least mentioned in above links, is using HttpServletRequestWrapper
class. I wrote, for my own, a class which wraps additional headers around the actual request instance.
public class RequestHeaderWrapper extends HttpServletRequestWrapper { public RequestHeaderWrapper( final HttpServletRequest request, final Map<String, List<String>> precedingHeaders, final Map<String, List<String>> succeedingHeaders) { super(request); headers = new HashMap<>(); if (precedingHeaders != null) { for (final String name : precedingHeaders.keySet()) { List<String> values = headers.get(name); if (values == null) { values = new ArrayList<>(); headers.put(name, values); } values.addAll(precedingHeaders.get(name)); } } for (final Enumeration<String> names = request.getHeaderNames(); names.hasMoreElements();) { final String name = names.nextElement(); List<String> value = headers.get(name); if (value == null) { value = new ArrayList<>(); headers.put(name, value); } value.addAll(Collections.list(request.getHeaders(name))); } if (succeedingHeaders != null) { for (final String name : succeedingHeaders.keySet()) { List<String> values = headers.get(name); if (values == null) { values = new ArrayList<>(); headers.put(name, values); } values.addAll(succeedingHeaders.get(name)); } } } @Override public String getHeader(final String name) { final List<String> values = headers.get(name); if (values != null && !values.isEmpty()) { return values.get(0); } return null; } @Override public Enumeration<String> getHeaders(final String name) { List<String> values = headers.get(name); if (values == null) { return Collections.emptyEnumeration(); } return Collections.enumeration(values); } @Override public Enumeration<String> getHeaderNames() { return Collections.enumeration(headers.keySet()); } private final Map<String, List<String>> headers; }
Full and latest source code is here and the apidocs is here.
Apache Maven
Please check the central if you’re using Apache Maven.
<dependency> <groupId>com.googlecode.jinahya</groupId> <artifactId>jinahya-ee</artifactId> <version>@@?</version> </dependency>
Usage
There are some factory methods for simple use.
public class MyFilter implements Filter { @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { final ServletRequest wrapper = RequestHeaderWrapper.newPrecedingInstance( requerst, "Accept", "application/xml"); chain.doFilter(wrapper, response); }
Redirect HTTP to HTTPS with Apache2
References
Redirect
Add following line to /etc/apache2/site-available/default
.
<VirtualHost *:80> .... Redirect permanent / https://{your.server.address}/ .... </VirtualHost>
Rewrite
Add following lines to /etc/apache2/site-available/default
.
<VirtualHost *:80> .... RewriteEngine on RewriteCond %{HTTPS} !=on RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L] .... </VirtualHost>
Chunked Transfer Encoding with HttpURLConnection
Not gonna work
public void chunkLikeChuckNorris(final HttpURLConnection connection, final InputStream input) { connection.setRequestProperty("Transfer-Encoding", "chunked"); assert "chunked".equals(connection.getRequestProperty("Transfer-Encoding")); // @@? final OutputStream output = connection.getOutputStream(); final byte[] buffer = new byte[8192]; for (int read = -1; (read = input.read(buffer)) != -1; ) { if (read == 0) { continue; } output.write(Integer.toHexString(read).getBytes("US-ASCII")); output.write('\r'); output.write('\n'); output.write(buffer, 0, read); output.write('\r'); output.write('\n'); } output.write('0'); output.write('\r'); output.write('\n'); }
We should do like this
public void chunkLikeRealChuckNorris(final HttpURLConnection connection, final InputStream input) { connection.setChunkedStreamingMode(-1); final OutputStream output = connection.getOutputStream(); final byte[] buffer = new byte[8192]; for (int read = -1; (read = input.read(buffer)) != -1; ) { output.write(buffer, 0, read); // just a plain stream copy } }
What about pre 1.5?
The HttpURLConnection.html#setChunkedStreamingMode(int) is since 1.5. I wonder how we used to do that.
인터넷 우체국 사업자 포털 오픈API
인터넷에서 우편번호 검색하는 서비스가 있나 하고 검색을 하던 중 반가운 페이지에 도착했다.
이름하여, “오픈API“!!!
아… 이제 우리나라도 이런거 되는구나! 역시!!!
기쁨은 아주 잠시 뿐, 실망스러운 부분이 너무도 많다.
References
다음 링크들을 참고하였다.
- 우체국 Open API 인코딩 문제
- 우체국 오픈 api 사용하기
- Classic ASP에서 우체국 우편번호 검색 API 사용하기
- Classic ASP에서 우체국 우편번호 API 사용하기 2
HTTP
- GET POST 둘 다 된다. (이건 좋은 건가?)
query
request parameter는 인코딩을 아예 안하거나(@@?)euc-kr
로 하면 된다.Accept-Language: ko
를 꼭 써줘야 한다.
XML
- 오랜만에 보는
encoding="euc-kr"
이다. - 아주 멋찐
<![CDATA[]]>
!!! - 역시나 XML Namespace를 기대하기는 어렵다.
- 특정 element localname(
{}postcd
)이 예시문서({}zipcode
)와 다르다. 서울 광진구 구의1동 248~252
이 따위로 주면 번지 구간을 어떻게 자르라는 건가?
How should be
EJB, JAX-RS
여기에 Stateless Session Bean과 JAX-RS Resource들을 구현해 놓았다. 참고하시길.
단 PostalCodesBean을 쓰려면 다음과 같이 ejb-jar.xml에 regkey값을 설정해줘야 한다.
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar xmlns = "http://java.sun.com/xml/ns/javaee" version = "3.1" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"> <enterprise-beans> <session> <ejb-name>PostalCodesBean</ejb-name> <env-entry> <env-entry-name>com.googlecode.jinahya.epost.openapi.PostalCodesBean/regkey</env-entry-name> <!--env-entry-type>java.lang.String</env-entry-type--> <env-entry-value>{your_regkey}</env-entry-value> </env-entry> </session> </enterprise-beans> </ejb-jar>
XML Schema
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" version="1.0" targetNamespace="http://jinahya.googlecode.com/epost/openapi" xmlns="http://jinahya.googlecode.com/epost/openapi" xmlns:tns="http://jinahya.googlecode.com/epost/openapi" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <xs:element name="postalCodes" type="postalCodes"/> <xs:complexType name="postalCode"> <xs:sequence> <xs:element name="address" type="xs:string"/> <xs:element name="code" type="xs:string"/> </xs:sequence> </xs:complexType> <xs:complexType name="postalCodes"> <xs:sequence> <xs:element name="postalCode" type="postalCode" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:schema>
XML
<?xml version="1.0" encoding="utf-8"?> <postalCodes xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://jinahya.googlecode.com/epost/openapi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <postalCode> <address>서울 광진구 구의1동 632~640</address> <code>143835</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 257</address> <code>143828</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 253~254</address> <code>143827</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 248~252</address> <code>143826</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 232~241</address> <code>143824</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 221~222</address> <code>143823</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 서울광진경찰서</address> <code>143703</code> </postalCode> <postalCode> <address>서울 광진구 구의1동</address> <code>143201</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 새한아파트</address> <code>143722</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 242~243</address> <code>143825</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 246</address> <code>143825</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 641~655</address> <code>143836</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 크레신타워3차</address> <code>143716</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 225~226</address> <code>143960</code> </postalCode> <postalCode> <address>서울 광진구 구의1동 229~231</address> <code>143962</code> </postalCode> </postalCodes>
Comments
<bluff>지금 내가 가는 길을 누군가 먼저 걷지 않았다면 내 발은 온통 자갈에 채여 멍이 들고 가시에 찔려 피를 흘리고 있을 것이다.</bluff>
— 나 —
아래는 그 ‘누군가’에 해당하는 분들의 말씀들이다.
어지간히 단가가 쌌던 모양이다.
— Dopany Soft —
여담이지만 우리나라 API제공은 전혀 친철하지 않다..
그냥 알아서 쓸려면 쓰고 말라면 말라는 식이다.우체국 API페이지도 들어가보면 참 부실하기 짝이 없는 도움말들..
뭐 이유야 훤히 보이는 거지만..
외부에 맞겨서 API개발하고 소스는 돌아가지만
그 시스템을 아는 사람은 우체국 쪽에 별로 없다..
그래서 도움말도 외부업체에서 받은 기술서가 전부고 그마저도 대충 보여주고 마는 거겠지…— 수유산장 —