Tistory View

Android Develop/helper

안드로이드 HttpUrlConnection POST 전송 #1

God Dangchy What should I do? 2020. 12. 24. 01:41

1. 안드로이드 http 다운로드하기( HttpURLConnection + SSL )

2. 안드로이드 HttpUrlConnection 서버로 전송 #1 (기본편, x-www-form-urlencoded )

3. 안드로이드 HttpUrlConnection 서버로 전송 #2 ( multipart/form-data )

 

 

 

 

서버에 데이터를 전송하려면 기본적으로 "application/x-www-form-urlencoded" 방식을 쓴다.

 

x-www-form-urlencoded의 구조는 다음과 같이 생겼다.

이름2=값1&이름2=값2&이름3=값3&이름4=값4

이름과 값이 한쌍을 이루며, 쌍을 분리하는 것은 '&', 이름과 값을 분리하는 것은 '='로 구분되어지게 된다.

 

여기서 이름과 값 모두 UrlEncoded라는 형식으로 입력을 해야 한다. 그 규칙은 다음과 같다.

 

1. 영문자와 숫자, '-', '_', '.'(쩜)은 그대로

2. 스페이스[아스키코드0x20]은  '+'로

3. 나머지는 %XX형태의 Hex값을 가지도록 변경을 해야한다.

 

따라서 만들어진 이름과 값에 있는 '&'나 '='이 3번에 따라 바뀌기 때문에, 구분자로 사용되는 '&'나 '='과 충돌문제가 발생하지 않는다.

 

 

이 3가지 변경작업을 해주는 녀석이 이미 안드로이드에는 함수로 들어가 있다.

String URLEncoder.encode( String value, String encoding )

value에 해당하는 것이 위에서 이름과 값이며, encoding은 서버에 프로그래밍된 방식을 넣어준다.

 

 

이제 전체 post데이터를 만드는 코드이다.

public static byte[] CreateQuery( List<Pair> pairs, String charset ) {

        StringBuilder result = new StringBuilder();
        boolean first = true;
        try {
            for (Pair pair : pairs) {

                if (first)
                    first = false;
                else
                    result.append('&');

                result.append(URLEncoder.encode((String) pair.first, charset));
                result.append('=');
                result.append(URLEncoder.encode((String) pair.second, charset));
            }
        }
        catch( Exception e ) {

        }

        return result.toString().getBytes();
    }

 

사용법은 다음과 같다.

List<Pair> params = new ArrayList<Pair>();
params.add(new Pair("이 름",  "한글1") );
params.add(new Pair("Name",  "abc def . - _ 한글"  ) );
                        
byte[] postData = CreaeQuery( params, "UTF-8");

postData에는 바이너리 형태의 "%EC%9D%B4+%EB%A6%84=%ED%95%9C%EA%B8%801&Name=abc+def+.+-+_+%ED%95%9C%EA%B8%80"값이 들어가 있다.

 

dx-www-form-urlencoded

 

RFC 3986을 따르는 규격이 있는 데, 이는 스페이스를 '+'대신 %20으로 바꾼다. 현재의 브라우저들은 주로 이 방법을 쓴다. 바뀐지 한참 됐다.
이 걸 쓰나 저 걸 쓰나 큰 차이가 없는 데, 이유는 서버에서 다시 풀 때는 같은 결과가 나오기에 어떤 것을 써도 상관이 없다.

 

Content-Length VS. Chunked

만약 전송할 데이터의 전체크기를 미리알고 있거나 빨리 계산을 할 수 있으면 Content-Length를 사용하는 것을 권장한다. 자동으로 만들어지는 데이터를 전송할 경우 Chuncked방식을 사용하는 것이 좋다.

 

기본적으로 업로드작업은 크기를 미리 설정하는 방식을 권장하고 있다. 서버에서 어떻게 처리해야 할지 수월하기 때문이다. 만약 Chunked방식을 사용한다면 서버는 언제 모든 데이터가 전송될지 모르니 서버마다 다르겠지만, 하염없이 메모리가 클라이언트, 서버 모두 증가할 수 있다.

 

 

 

 

Content-Length방식을 쓰려면 setFixedLengthStreamingMode함수를 쓴다.

void setFixedLengthStreamingMode (int contentLength)

contentLength에 위에서 만들어진 크기를 넣어주면 된다. [postData.length]

그려면, HttpURLConnection(HttpsURLConnection)은 Header로 Content-Length를 서버에 보내게 된다.

 

 

전송할 데이터를 만들면서(on-the-fly) 전송하려면, 특히 크기를 계산하기 어려울때는  setChunkedStreamingMode함수를 쓴다.

void setChunkedStreamingMode (int chunklen)

Chunck크기를 기본값을 쓰려면 그냥 0이하의 값을 넣어주면 된다. 대부분의 상황에서 그냥 0을 넘기는 것으로 할일은 다 한 것이다. (HttpUrlConnection이 알아서 HTTP 헤더에 Transfer-Encoding값을 chunked로 설정한다.)

 

setFixedLengthStreamingModesetChunkedStreamingMode는 선택해서 한개만 쓰는 것이 맞다.

둘중 한개를 사용하면 다른 모드로 바꿀 수 없는 것 같다.

 

만약 setFixedLengthStreamingMode나 setChunkedStreamingMode를 사용하지 않으면 기본적으로 Content-Length로 처리되는 것 같다. 따라서 아주 큰 데이터를 서버에 보낸다고 해도 Content-Length값을 알아야 하기 때문에 보내기 전에 모든 데이터를 버퍼링해서 전송크기를 계산 후 전송되게 되는 것 같다.

setFixedLengthStreamingMode나 setChunkedStreamingMode를 사용하면 redirect나 BasicAuthentication이 자동으로 동작되지 않는다.

 

 

 

전송코드( Content-Length방식)

List<Pair> params = new ArrayList<Pair>();
params.add(new Pair("이 름",  "한글1") );
params.add(new Pair("Name",  "abc def . - _ 한글"  ) );


byte[] postData = CreateQuery(params, "UTF-8");



url = new URL("접속할 URI");
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(true);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

urlConnection.setFixedLengthStreamingMode( postData.length );


// 데이터를 전송한다.
out = new BufferedOutputStream( urlConnection.getOutputStream() );
out.write( postData );
out.close();
out = null;


// 서버에서 응답데이터처리
// 
if( 200 != urlConnection.getResponseCode() ) {
 throw new Exception( "Not Ok : " + urlConnection.getResponseCode() );
}


BufferedInputStream in  = new BufferedInputStream(urlConnection.getInputStream());
InputStreamReader   rdr = new InputStreamReader( in, charset );

int nread = rdr.read( buf );
while ( nread > 0 ) {
 contents.append( buf, 0, nread );
 nread = rdr.read( buf );
}

in.close();


contents.toString(); // 서버의 응답내용

 

필자가 테스트한 바로는, 실제 위 코드에서 setFixedLengthStreamingMode를 사용하지 않아도 Content-Length가 전송이 되었다. 위에서 설명한대로, setFixedLengthStreaminMode를 쓰지 않으면, 전송할 Stream을 전부 받은 후에나 실제 전송이 일어나고 있다는 것이다. 전송할 크기를 알고 있다면 부하를 줄이기 위해 setFixedLengthStreamingMode함수로 크기를 지정해야 한다.

 

 

전송코드( Chunked방식)

List<Pair> params = new ArrayList<Pair>();
params.add(new Pair("이 름",  "한글1") );
params.add(new Pair("Name",  "abc def . - _ 한글"  ) );


// byte[] postData = CreateQuery(params, "UTF-8"); on-the-fly 할 것이다.


url = new URL("접속할 URI");
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(true);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

urlConnection.setChunkedStreamingMode(0);     // <-- 달라진 점


// 데이터를 전송한다.
out = new BufferedOutputStream( urlConnection.getOutputStream() );

boolean first = true;

for (Pair pair : pairs) {
	if (first) {
    		first = false;
	}
	else {
    		out.write( '&' );
	}

	out.write( URLEncoder.encode((String) pair.first,  'UTF-8' ).getBytes() );
	out.write( '=' );
	out.write( URLEncoder.encode((String) pair.second, 'UTF-8' ).getBytes() );

}

out.close();
out = null;


// 서버에서 응답데이터처리 - 이하는 Content-Length코드와 같음

 

 

 

QnA

Q. Chunked방식을 쓸 때, Chunked 형태로 만들어서 전송해야 되나요?

A. 아니요. HttpUrlConnection이 알아서 Chunked형식으로 바꿔서 전송한다. 그냥 Data만 계속 밀어넣어주면 된다.

 

Q. x-www-form-urlencoded를 쓰는 이유가 있나요?

A. 첫번째로는 아주 오래된 이야기인데(한 50년된 이야기다), 데이터를 전송할 때, binary를 전송하지 못하는 기계가 있었다. 그래서 이걸 억지도 ASCII로 바꾸고 전송한 다음 다시 binary로 바꾸는 일이 그 때는 꼭 필요한 작업이었다. 그 때의 호환성을 위해 지금도 이 ASCII로 변환하는 작업을 하게 된 것이다. base64, uuencode같은 것이 만들어진 이유와 같다.

두번째 이유는 현재 돌고 있는 서버프로그램들(PHP, JSP등등)이 이 방식으로 넘어온 데이터를 서버프로그램 자체적으로 해석하여 서버프로그래밍을 할 때, 다시 binary로 바꾸는 과정을 코딩하지 않아도 된다. 다른 방식을 쓰면 일일이 바꿔줘야한다. 쉽게 말해서 프로그래밍 호환성문제로 이 방식을 쓴다.

Replies
Reply Write