http 400 Error 는 클라이언트쪽에서 발생한 Error 로 서버가 Client 에 이슈가 있어서 request 를 처리할 수 없을 때 발생하는 Error 이다. 이 에러를 처음 겪었을 때 내가 얻은 로그는 단 한줄 이었다.

09-24 17:18:39.847 D 22437    22437    MainActivity:    Status Code : 400 

 

당연히 뭐가 문제인지 알 수 없었는데 더 많은 정보를 얻기 위해서는 로그를 추가적으로 획득 하여야 한다.

이 때 사용하는게 loggin intercepter 이다.

 

build.gradle 에 아래의 라이브러리를 추가해준다.

Module: app

dependencies {
	...
	implementation 'com.squareup.okhttp3:logging-interceptor:3.12.1'
	...
}

Retrofit 객체에 intercepter 가 달린 client를 등록시켜준다.

OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
clientBuilder.addInterceptor(loggingInterceptor);

Log.d(TAG,"initMyAPI : " + baseUrl);
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(baseUrl)
        .addConverterFactory(GsonConverterFactory.create())
        // client 를 등록해준다.
        .client(clientBuilder.build())

        .build();

mMyAPI = retrofit.create(MyAPI.class);

 

등록을 시킨 후 문제가 되는 request 를 하고 log 를보면 아래의 로그를 확인 할 수 있다.

log 의 TAG 는 OkHttp 이다.

09-24 17:26:51.977 D 29369    29369    MainActivity:     POST 
09-24 17:26:52.003 D 29369    30394    OkHttp      : --> POST http://27ad1bf3.ngrok.io/posts/ 
09-24 17:26:52.003 D 29369    30394    OkHttp      : Content-Type: application/json; charset=UTF-8 
09-24 17:26:52.004 D 29369    30394    OkHttp      : Content-Length: 24 
09-24 17:26:52.004 D 29369    30394    OkHttp      : {"title":"Android text"} 
09-24 17:26:52.005 D 29369    30394    OkHttp      : --> END POST (24-byte body) 
09-24 17:26:52.779 D 29369    30394    OkHttp      : <-- 400 Bad Request http://27ad1bf3.ngrok.io/posts/ (774ms) 

나 같은 경우에는 서버에 선언된 모델이 title 과 text, 두개의 field 를 가지고 있는데 현재 request 한 객체를 보니 title 만 set을 해주고 post를 해주고 있었다. 서버의 field 값과 post 한 객체 데이터를 동일하게 바꿔주니 성공적으로 post 가 되었다.

 

 

'Android' 카테고리의 다른 글

[Android] Retrofit 을 이용한 RestAPI Android 연동 예제  (5) 2019.09.24

https://freekim.tistory.com/4

 

Django REST framework 예제

1. Restframework 설치 - 터미널에 아래의 명령어를 입력시켜 준다. pip install djangorestframework - settings.py 에 rest framework 추가 해준다. INSTALLED_APPS = [ 'django.contrib.admin', 'django.contri..

freekim.tistory.com

위의 예제에서 작성된 django 서버와 연동하는 예제이다.

Server 쪽의 예제가 필요하면 위의 링크를 참고 하시길.

 

Retrofit ?

 - 서버에 Restful API 에 맞는 요청을 보내기 위해 사용하는 위한 통신 인터페이스이다. AsyncTask 와 HttpUrlConnection 을 사용하여 구현 할 수는 있지만 기본적으로 필요한 요구사항들을 쉽게 처리할 수 있게하는 도구이다.

 

 

Retrofit의 github 이다 예제도 잘 나와 있다.

http://devflow.github.io/retrofit-kr/

 

Retrofit - 한글 문서

A type-safe HTTP client for Android and Java

devflow.github.io

1. build.gradle 추가하기

현재 2.6.1 이 최신 버전이라 최신버전을 추가 했다.

Module: app


dependencies {
    ....
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
    ....
}

2. Manifest Permission 등록

internet을 사용하므로 internet permission 을 추가해준다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.retrofitexample">

    <uses-permission android:name="android.permission.INTERNET"/>
    
    ...
    
</manifest>

3. Data 객체 정의

우리가 server의 model에서 구성했던것과 동일하게 구성해준다.

PostItem.java

package com.example.retrofitexample;

public class PostItem {
    private String title;
    private String text;

    public String getText() {
        return text;
    }

    public String getTitle(){
        return title;
    }

    public void setTitle(String s){
        title = s;
    }

    public void setText(String s){
        text = s;
    }
}

4. Retrofit Interface 구성

Call 을 통해서 웹서버에 요청을 보낼 수 있다.

1. Http요청을 어노테이션으로 명시

2. URL Parameter와 Query Parameter 사용이 가능하다.

 ex) @GET("/group/{id}/users") Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort)

3. 객체는 Body 로 json형태로 전달한다.

url 끝에 / 를 빼먹으면 error 가 발생할 수 있으니 유의바란다.

 

MyAPI.java

package com.example.retrofitexample;

import java.util.List;

import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.PATCH;
import retrofit2.http.POST;
import retrofit2.http.Path;

public interface MyAPI{

    @POST("/posts/")
    Call<PostItem> post_posts(@Body PostItem post);

    @PATCH("/posts/{pk}/")
    Call<PostItem> patch_posts(@Path("pk") int pk, @Body PostItem post);

    @DELETE("/posts/{pk}/")
    Call<PostItem> delete_posts(@Path("pk") int pk);

    @GET("/posts/")
    Call<List<PostItem>> get_posts();

    @GET("/posts/{pk}/")
    Call<PostItem> get_post_pk(@Path("pk") int pk);
}

5. 동작 확인을 위한 초기화 및 간단한 동작 구현

단순 동작 확인 을 위하여 MainActivity 에서 모두 구현하였다.

부분 부분 설명 후 전체 코드는 밑에 있다. 하나씩 따라해 보자.

 

Retrofit 객체를 생성하고 이 객체를 이용해서, API service 를 create 해준다.

private void initMyAPI(String baseUrl){
    Log.d(TAG,"initMyAPI : " + baseUrl);
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    mMyAPI = retrofit.create(MyAPI.class);
}

내가 정의한 api 중에서 get 동작을 살펴보자.

mMyAPI.get_posts(); 함수를 이용해서 Call 객체를 불러온다.

Call 객체에 Callback을 넣어준다. 응답이 왔을 때에는 onResponse 함수로 가게 되고 응답이 안왔으면 Throwable 객체에 메시지를 담아서 onFailure 가 호출된다.

성공적인 response 가 도착 했다면 response.body() 에 우리가 서버로부터 받아온 데이터가 들어있다.

Call<List<PostItem>> getCall = mMyAPI.get_posts();
getCall.enqueue(new Callback<List<PostItem>>() {
    @Override
    public void onResponse(Call<List<PostItem>> call, Response<List<PostItem>> response) {
        if( response.isSuccessful()){
            List<PostItem> mList = response.body();
            String result ="";
            for( PostItem item : mList){
                result += "title : " + item.getTitle() + " text: " + item.getText()+ "\n";
            }
            mListTv.setText(result);
        }else {
            Log.d(TAG,"Status Code : " + response.code());
        }
    }

    @Override
    public void onFailure(Call<List<PostItem>> call, Throwable t) {
        Log.d(TAG,"Fail msg : " + t.getMessage());
    }
}

GET Button Listener 를 달아 위의 event를 등록을 하고 GET Button을 누르면 아래와 같은 결과를 볼 수 있다.

나머지 동작은 아래 Code로 첨부하니 동작 확인 해보시길..

모든 동작을 확인 한 후 서버의 상태이다.

 

MainActivity.java

package com.example.retrofitexample;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private final  String TAG = getClass().getSimpleName();

    // server의 url을 적어준다
    private final String BASE_URL = "http://27ad1bf3.ngrok.io";
    private MyAPI mMyAPI;

    private TextView mListTv;

    private Button mGetButton;
    private Button mPostButton;
    private Button mPatchButton;
    private Button mDeleteButton;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mListTv = findViewById(R.id.result1);

        mGetButton = findViewById(R.id.button1);
        mGetButton.setOnClickListener(this);
        mPostButton = findViewById(R.id.button2);
        mPostButton.setOnClickListener(this);
        mPatchButton = findViewById(R.id.button3);
        mPatchButton.setOnClickListener(this);
        mDeleteButton = findViewById(R.id.button4);
        mDeleteButton.setOnClickListener(this);

        initMyAPI(BASE_URL);
    }

    private void initMyAPI(String baseUrl){

        Log.d(TAG,"initMyAPI : " + baseUrl);
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        mMyAPI = retrofit.create(MyAPI.class);
    }


    @Override
    public void onClick(View v) {
        if( v == mGetButton){
            Log.d(TAG,"GET");
            Call<List<PostItem>> getCall = mMyAPI.get_posts();
            getCall.enqueue(new Callback<List<PostItem>>() {
                @Override
                public void onResponse(Call<List<PostItem>> call, Response<List<PostItem>> response) {
                    if( response.isSuccessful()){
                        List<PostItem> mList = response.body();
                        String result ="";
                        for( PostItem item : mList){
                            result += "title : " + item.getTitle() + " text: " + item.getText() + "\n";
                        }
                        mListTv.setText(result);
                    }else {
                        Log.d(TAG,"Status Code : " + response.code());
                    }
                }

                @Override
                public void onFailure(Call<List<PostItem>> call, Throwable t) {
                    Log.d(TAG,"Fail msg : " + t.getMessage());
                }
            });
        }else if(v == mPostButton){
            Log.d(TAG,"POST");


            PostItem item = new PostItem();
            item.setTitle("Android title");
            item.setText("Android text");
            Call<PostItem> postCall = mMyAPI.post_posts(item);
            postCall.enqueue(new Callback<PostItem>() {
                @Override
                public void onResponse(Call<PostItem> call, Response<PostItem> response) {
                    if(response.isSuccessful()){
                        Log.d(TAG,"등록 완료");
                    }else {
                        Log.d(TAG,"Status Code : " + response.code());
                        Log.d(TAG,response.errorBody().toString());
                        Log.d(TAG,call.request().body().toString());
                    }
                }

                @Override
                public void onFailure(Call<PostItem> call, Throwable t) {
                    Log.d(TAG,"Fail msg : " + t.getMessage());
                }
            });
        }else if( v == mPatchButton){
            Log.d(TAG,"PATCH");
            PostItem item = new PostItem();
            item.setTitle("android patch title");
            item.setText("android patch text");
            //pk 값은 임의로 하드코딩하였지만 동적으로 setting 해서 사용가능
            Call<PostItem> patchCall = mMyAPI.patch_posts(1,item);
            patchCall.enqueue(new Callback<PostItem>() {
                @Override
                public void onResponse(Call<PostItem> call, Response<PostItem> response) {
                    if(response.isSuccessful()){
                        Log.d(TAG,"patch 성공");
                    }else{
                        Log.d(TAG,"Status Code : " + response.code());
                    }
                }

                @Override
                public void onFailure(Call<PostItem> call, Throwable t) {
                    Log.d(TAG,"Fail msg : " + t.getMessage());
                }
            });


        }else if( v == mDeleteButton){
            Log.d(TAG,"DELETE");
            // pk 값은 임의로 변경가능
            Call<PostItem> deleteCall = mMyAPI.delete_posts(2);
            deleteCall.enqueue(new Callback<PostItem>() {
                @Override
                public void onResponse(Call<PostItem> call, Response<PostItem> response) {
                    if(response.isSuccessful()){
                        Log.d(TAG,"삭제 완료");
                    }else {
                        Log.d(TAG,"Status Code : " + response.code());
                    }
                }

                @Override
                public void onFailure(Call<PostItem> call, Throwable t) {
                    Log.d(TAG,"Fail msg : " + t.getMessage());
                }
            });
        }
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/result1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="get"/>
    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="post"/>
    <Button
        android:id="@+id/button3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="patch"/>
    <Button
        android:id="@+id/button4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="delete"/>




</LinearLayout>

'Android' 카테고리의 다른 글

[Android] Retrofit http 400 Error 확인 방법  (0) 2019.09.25

장고 server 를 테스트 하기 위해서 웹을 사용한다면 로컬에서도 충분히 확인을 할 수 있지만

IOS 나 Android 개발을 할 때 에 서버가 필요한 경우가 있다.

물론 돈을 내고 호스팅을 하거나 집에 서버 컴퓨터가 있으면 걱정이 없겠지만

단순히 테스트 용도로 사용하기에는 무리가 있다.

여러 방법을 찾던중 ngrok 을 사용하여 5분만에 내 컴퓨터를 테스트용 홈서버로 만드는 방법을 소개 하고자 한다.

 

1. ngrok download

https://ngrok.com/download 에 접속하여 ngork 을 다운로드 받는다.

 

2. django server 실행

 - 아래의 명령어로 장고 서버를 실행해 준다. 0.0.0.0:8000 의미는 현재 요청된 호스트의 IP나 이름에 상관없이 실행한다는 의미이다.

$ python manage.py runserver 0.0.0.0:8000

 - django project의 settings.py 수정

ALLOWED_HOSTS = ['*']

3. ngrok 실행

 - ngrok을 실행해보면 아래의 커맨드 화면이 보인다.

그리고 내가 연 포트번호를 넣어 아래의 명령어를 실행해준다.

>ngrok http 8000

 주소가 부여되는데 핸드폰 단말에서 해당 주소로 들어가면 내가 구성해놓은 rest api 페이지를 볼 수 있다.

 

ngrok 은 도메인을 할당하는 것 처럼 ngrok 을 통해서 고정 아이피와 나의 서버를 바인딩 해준다. 다른 서버를 들렸다 오는 것이므로 속도가 느리고 테스트용도로만 사용하자.

1. Restframework 설치

 - 터미널에 아래의 명령어를 입력시켜 준다.

pip install djangorestframework

 - settings.py 에 rest framework 추가 해준다.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
]

2. 어플리케이션 생성

 - 어플리케이션을 생성한다.

$ python manage.py startapp myapp

 - setting.py 에 추가한다.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'myapp'
]

3. 모델 생성

 myapp/models.py 에 간단하게 아래의 post model class 를 추가해준다.

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    text = models.TextField()

아래의 명령어로 데이터 베이스에 모델을 위한 테이블을 형성한다.

$ python manage.py makemigrations myapp
$ python manage.py migrate

4. Serializers

쿼리셋, 모델 인스턴스 등의 복잡한 데이터를 json, xml 등의 데이터로 변환해준다.

-  myapp/serializers.py 생성 후 아래의 코드를 입력한다.

from rest_framework import  serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ('title', 'text')

 

5. Views

 Viewset 하나에 다양한 method를 포함하고 있다.

 - myapp/views.py 수정한다.

from rest_framework import viewsets
from .serializers import PostSerializer
from .models import Post

class PostViewset(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

 

6. urls 

router 를 설정해 준다. router 는 URL을 자동으로 맵핑해준다.

 - project/urls

from rest_framework import routers
from myapp import views
from django.conf.urls import url, include

router = routers.DefaultRouter()
router.register(r'posts', views.PostViewset)


urlpatterns = [
    url(r'^',include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

 

7. 서버 실행 후 동작 확인

$ python manage.py runserver

 - http://127.0.0.1:8000/

서버를 실행 후 위 주소로 들어가면 Default router에 의해서 접근할 수 있는 URL 인 http://127.0.0.1:8000/posts가 표시된다.

http://127.0.0.1:8000/posts에 접속을 해보면 아래와 같이 post list를 볼 수 있다. 현재는 데이터를 하나도 등록을 안시켰으므로 비어 있다.

하단의 title 과 text 에 test 문구를 넣어주고 post 버튼을 눌러보자. post 를 누르면 post된 data만 보이게되고

다시 오른쪽 상단의 get버튼으로 list를 다시 받아오면 아래와 같이 내가 등록했던 data list를 볼 수 있다.

 

기본적으로 rest framework 을 구성해 보았다. 

다음 시간에는 안드로이드와 연결되서 실제로 어떻게 쓸 수 있는지 알아보고자 한다.

 

<!DOCTYPE html>
<html>
<head>
	<title>test html 입니다.</title>
</head>
<body>

</body>
</html>

티스토리 블로그에 코드를 공유 하는 글들을 보면 위의 예시 처럼

Editor tool 과 동일하게 high light 도 들어가 있고, Line Number 까지 들어가 있는 글이

가독성이 좋았고 보기 편했다.

 

티스토리는 현재 코드 블록이라는 것을 제공해서 손쉽게 코드를 넣을 수 있게 제공을 하고 있다.

다만 기본 테마가 너무 못생겨서 highlight 와 line number 을 새로 추가해줘야한다.

 

다른 블로그에도 글들이 많이 있어 다른 글들을 보아도 좋으나

초보자의 입장에서 처음 부터 코드블럭을 넣고, 라인넘버까지 넣는 일련의 과정을 한번에 정리해 보았다.

 

1. 코드 블럭을 통해 코드 추가.

1. 글쓰기를 누르고 난 후 상단 메뉴를 보면 코드 블럭이라는 메뉴가 보이고 클릭을 한다.

2. 노란색깔로 표시된 부분을 누르면 내가 작성한 코드의 언어를 선택할 수 있다. 그리고 확인을 누르면

아래와 같은 형식으로 보이게 되고 완료를 누르면 최종적으로 코드가 코드블럭의 기본 테마 형식으로 보이게 된다.

<결과>

기본 코드 블록의 결과는 정말 볼품이 없다. 이제 HighLight 를 추가해보자.

 

2. Highlight.js 적용하기

Highlight.js 는 블로그 관리 에서 플러그인을 통해 적용하는 방법과 HTML 을 통해서 직접 적용하는방법이 있다.

플러그인을 활용한 방식은 간단하므로 설명을 생략하고

직접 사이트에서 받아와 HTML에 적용하는 방법을 설명 하겠다.

 

 

테마는 아래의 사이트를 보고 고르면 된다.

나는  AndroidStudio 테마가 익숙하므로 AndroidStudio 테마를 선택했다.

https://highlightjs.org/static/demo/

테마를 선택했다면 이제 HTML에 적용해보자.

 

내 블로그의 HTML 편집에 들어가서 </head> 위에 아래의 코드를 복사 붙여넣기 해주면된다.

그리고 선택한 테마이름에서 대문자는 소문자로, 띄워쓰기는 -로 바꿔주고 아래의 androidstudio 부분 대신

입력해주면 된다.

예)

'A 11 Y Dark' - > a-11-y-dark

cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/styles/a-11-y-dark.min.css">

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/styles/androidstudio.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>

 

초보자를 위해 과정을 스크린샷했다.

1. 블로그 관리 페이지에서 [꾸미기]->[스킨편집] 을 누른다.

 

2. 오른쪽 [html편집] 버튼을 누른다.

 

3. </head>를 검색 하여 테마 이름을 수정하여 블록 영역처럼 </head>위에 붙여 넣는다.

4. 저장을 해보면 위와 같은 결과를 얻게된다.

 

여러분과 결과물이 다를 수 있는데 나는 추가적으로 css에 들어가서 <code> css의 font size를 13으로 바꾸고, 적용되어 있던 font 를 삭제해줫다.

 

3. LineNumber 추가하기

1. highlightjs-line-number.js 를 다운 받는다. 아래는 git hub 주소이다.

혹은 ----> 이곳  <--- 을 우클릭 하여 다른 이름으로 링크저장을 누른다.

https://github.com/wcoder/highlightjs-line-numbers.js/

 

wcoder/highlightjs-line-numbers.js

Line numbering plugin for Highlight.js . Contribute to wcoder/highlightjs-line-numbers.js development by creating an account on GitHub.

github.com

2. 다운받은 js 파일을 위에 highlight 적용할때 사용한 [html편집] 으로 들어가

상단 [파일업로드] -> [추가]  를 눌러 js 파일을 내 블로그에 추가해준다.

3. js 가 추가가 되었으면 아래의 코드를 highlight 를 적용했을 때와 동일하게 </head> 위에 복사 붙여넣기 해준다.

<script src="./images/highlightjs-line-numbers.js"></script>
<script> hljs.initLineNumbersOnLoad();
	$(document).ready(function() {
		$('code.hljs').each(function(i, block) {
		hljs.lineNumbersBlock(block);
		});
	});
</script>

 

4.

완성되었다. 

 

이제 꾸준히 예제 코드들을 열심히 올려주기만 하면된다.

파이팅

+ Recent posts