URLEncoder.encode 의 버그(?)

Posted 2014. 5. 27. 14:37

아래와 같이 HTTPURLConnection을 이용하여 파일을 다운로드하는 개발을 한다고 할때 파일명에 공백문자가 있으면 400 오류가 발생한다.

처음엔 IIS의 문제인줄 알고 MS에 문의도 해보았으나 결국은 java의 URLEncoder클래스의 공백처리가 웹서버가 인식하는 것과 달라서 발생하는 것이었다.

 

public InputStream getHttpStreamData(String serverAddress, String postParam, String requestMethod) throws Exception {
  
  long start = System.currentTimeMillis();
  
  // 접속할 HTTP URL 파싱
  HttpURLConnection conn = null;
  InputStream is = null;
  try {
   URL url = new URL(serverAddress);
   
   // URL에 연결 및 초기화
   conn = getHttpURLConnection(url);
   conn.setRequestMethod(requestMethod);
   conn.setDoInput(true);
   conn.setUseCaches(false);

 

   if(requestMethod.equals("POST")){

    conn.setDoOutput(true);
    OutputStream out_stream = conn.getOutputStream();
    
    out_stream.write( postParam.getBytes("UTF-8") );
    out_stream.flush();
    out_stream.close();
    
   }
   
   if (conn.getResponseCode() == 200) {
     is = conn.getInputStream();
   } else {
    logger.error( url.getQuery() + " Response : \n ResponseCode is "+conn.getResponseCode()+"& ResponseMessage is "+conn.getResponseMessage());
    throw new JsonException("9998", "요청처리중 오류가 발생했습니다.");
   }
   
  } catch (Exception e) {
   logger.error( " ##################################################### ");
   logger.error("ResponseCode is [[[["+conn.getResponseCode()+"]]]]");
   logger.error("ResponseMessage is [[[["+conn.getResponseMessage()+"]]]]");
   logger.error( " ##################################################### ");
   e.printStackTrace();
   throw new JsonException("9999", e.getMessage());
  }
  
  long end = System.currentTimeMillis();
  logger.warn("#########################################################");
  logger.warn( server_addr);
  logger.warn("######## elapsed time is [[[[[ " + (end - start) + " ]]]]] ms #######");
  logger.warn("#########################################################");
  
  return is;
  
 }

 

 

이런경우는 넘겨진 URL에 공백문자가 존재할 경우  "+"로 치환된 encoding 결과를 수동으로 '%20'으로 변경해 주어야 한다.

 

fileUrl = fileUrl.substring(0, lastSlashIndex) + URLEncoder.encode(fileUrl.substring(lastSlashIndex),"UTF-8").replaceAll("\\+", "%20");

 

logger.debug("RESULT>>>>>" + fileUrl);

 

 

RFC 규약에 의하면 위의 처리는 미완성이다.

다음의 블로그 글을 참고해 보면 제대로 된 URL 인코딩을 처리하려면 몇가지 문자를 더 수동으로 처리해 주어야 한다.

 

http://blog.naver.com/PostView.nhn?blogId=jogakdal&logNo=129088614&redirect=Dlog&widgetTypeCall=true

 

위의 글에 의거 앞의 메소드는 다음과 같이 처리하도록 변경했다.

 

fileUrl = fileUrl.substring(0, lastSlashIndex) + URLEncoder.encode(fileUrl.substring(lastSlashIndex),"UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");

==> 그런데 확인해보니 오히려 이렇게 변경하면 또다른 문제가 발생하는 듯 하다. 다시 원복.

 

참고로 서버와의 통신에서 인코딩된 결과를 확인하기 위해서는 다음과 같은 유틸프로그램을 사용한다.

 

http://www.microsoft.com/en-us/download/details.aspx?id=4865

 

MS에서 제공하는 네트워크 모니터링 프로그램이다.

이 프로그램을 서버와 로컬 PC에 설치한뒤에 http에 대한 부분을 모니터링하도록 설정하고 start를 누른다.

이후부터 request, response 결과가  모니터링된다.

비슷한 유형의 프로그램이 많이 나와있으니 참고하기 바란다.

 

 

 

 

 

 

 

 

 



1. web.xml 에서 다음과 같이 처리합니다.

흔히 사용하던 jsp 경로 대신 REST uri경로를 그대로 사용합니다.

<error-page>

    <error-code>404</error-code>

    <location>/errors/404</location>

  </error-page>

  

  <error-page>

    <error-code>405</error-code>

    <location>/errors/405</location>

  </error-page>


2. ErrorController를 작성합니다.

package common.error


import java.util.HashMap;

import java.util.Map;


import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


import org.springframework.http.HttpStatus;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.servlet.ModelAndView;


@Controller

public class ErrorController {

private String RESULT_CODE = "result_code";

private String RESULT_MESSAGE = "result_message";

private String HEAD = "head";


@RequestMapping(value = "/errors/{errorCode}", method = RequestMethod.GET)

public ModelAndView error405(@PathVariable("errorCode") int errorCode, HttpServletRequest request, HttpServletResponse response) {

Map<String, Object> responseMap = new HashMap<String, Object>();

Map<String, Object> responseHeadMap = new HashMap<String, Object>();

                //넘겨받은 errorCode에 해당하는 HttpStatus Object를 가져옵니다.

HttpStatus httpStatus = HttpStatus.valueOf(errorCode); 

responseHeadMap.put(RESULT_CODE, httpStatus.value());

responseHeadMap.put(RESULT_MESSAGE, httpStatus.getReasonPhrase());

responseMap.put(HEAD, responseHeadMap);

/*

for (HttpStatus status :HttpStatus.values()) {

System.out.println(status.value() +"," + status.name()+"::" + status.getReasonPhrase());

}

*/

return new ModelAndView("jsonView", responseMap);

}

}


3. 결과는 다음과 같이 리턴합니다. (404/405, 500 등 각각의 error코드에 맞게 error메시지가 리턴됩니다.)

{

    "head": {

        "result_message": "Method Not Allowed",

        "result_code": 405

    }

}


P.S. 위의 리턴타입은 org.springframework.web.servlet.view.BeanNameViewResolver 를 이용하여 MappingJacksonJsonView를 선언하여 처리한 것입니다.

<bean id="beanNameViewResolver" class="org.springframework.web.servlet.view. BeanNameViewResolver" />

<bean id="jsonView" class="org.springframework.web.servlet.view.json. MappingJacksonJsonView">





Nexus를 사용하기 위해 공유기에서 외부로 포트포워딩을 설정하여 사용해 왔습니다. 

그러다가 Redmine이 도입되고 각각의 포트포워딩보단 하나로 합쳐야할 필요가 생겼습니다.

Nexus를 설치하는 방법은 두가지입니다.

첫째는 jetty를 포함한 설치, 두번째는 war 형태의 프로젝트로 기존 설치된 was에 추가하는 방법.

저는 첫번째 jetty를 포함한 설치로 진행했습니다.


1. Redmine ==> /

2. SVN    ==> /svn

3. Nexus  ==> /nexus


설정 및 연동해야할 부분은 위와 같습니다.


이렇게 되면 공유기 포트포워딩은 하나만 설정해도 끝납니다.


1. Redmine설치 및 SVN 연동에 대한 부분은 이전 포스팅을 참고하면 됩니다.


==> http://gubok.tistory.com/351

 ==> http://gubok.tistory.com/352

 ==> http://gubok.tistory.com/370



2. Nexus 연동

우선 nexus설치는 특별한 이상이 없는한 한번에 설치가 끝나기 때문에 이에 대한 설명은 Pass~

apache-jetty 연동방법은 크게 세가지가 있습니다. (jetty wiki 사이트에 나왔있습니다.)


1. Using Apache mod_proxy and an normal Jetty HTTP connector. 

2. Using Apache mod_proxy_ajp and the Jetty AJP connector. 

3. Using Apache mod_jk and the Jetty AJP connector. 


이중에서 첫번째 proxy를 이용한 연동방법으로 설정하도록 하겠습니다. (권장이라고 하네요.)

아래 링크는 apache 사이트에서 제공하는 mod_proxy 설정방법입니다.
http://httpd.apache.org/docs/2.2/mod/mod_proxy.html



1번의 과정을 정상적으로 거쳤다면 연동에 필요한 xxx.so 파일은 모두 로딩되어 있다는 전제로 진행합니다.


Proxy설정을 별도의 파일로 저장해서 생성합니다.


ProxyPass /nexus http://127.0.0.1:8787/nexus

ProxyRequests On

ProxyVia On


<Proxy *>

 Order deny,allow

 Allow from all

</Proxy>


ProxyPreserveHost On

ProxyStatus On


httpd.conf 에 위의 설정파일을 include해줍니다.


# Proxy Setting for Jetty

Include conf/extra/httpd-proxy-jetty.conf



/nexus로 들어오는 요청을 :8787/nexus로 처리하겠다는 의미입니다.






« PREV : 1 : ··· : 14 : 15 : 16 : 17 : 18 : 19 : 20 : ··· : 61 : NEXT »