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

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


다음 링크들을 참고하였다.

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개발하고 소스는 돌아가지만
그 시스템을 아는 사람은 우체국 쪽에 별로 없다..
그래서 도움말도 외부업체에서 받은 기술서가 전부고 그마저도 대충 보여주고 마는 거겠지…

— 수유산장 —