반응형

스프링에서 okhttp3 를 사용중 로그에 다음과 같은 오류 메시지가 떴다.

 

okhttp3 사용 중에 간헐적으로 unexpected end of stream on [주소] 가 나왔다.

 

request 를 요청받는 서버 설정마다 다른 것 같다.

해당 원인은

요청받는 서버의 keep_alive timeout 이 okhttp 로 요청하는 클라이언트 서버보다 timeout 시간이 낫기 떄문이다.

다시 말하면 클라이언트 서버가 timeout 이 받아주는 서버 timout 보다 길다.

 

나의 경우는 Okhttp3 로 요청하고 나서 프로세스가 조금 긴 경우에 다음과 같은 현상이 나왔다.

 

클라이언트가 서버에서 데이터를 받고나서 서버의 keep_alive timeout 시간이 지났음에도 클라이언트에서는 프로세스가 끝나지 않기 때문에 발생한다.

이런 경우 간단하게 해결 방법은 okhttp request 객체에 

addHeader("Connection","close") 

만 추가해주면 됐다.

 

Request request = new Request.Builder()
                .url(requestUrl)
                .addHeader("Connection", "close")
                .get()
                .build();

 

 

 

나의 경우 서버에서 데이터를 받아와서 처리하는데 많은 로직이 있어 처리시간이 짧지 않았고, 프로세스를 처리 하는 도중 요청받는 서버의 keep_alive timeout 시간이 지나 다음 오류를 뱉었다.

데이터를 받아 온 후라 다음 오류가 나도 상관없었지만, 오류를 모니터링 할때 보기 안좋기 때문에 해결해 주었다.

 

예를 들어 keep_alive timeout 이 긴 서버의 경우는 해당 오류가 나지 않을 것이다.

 

참고문헌

github.com/square/okhttp/issues/2738

 

반응형
반응형

Gson 라이브러리 및 Jackson 사용하여 Json String 을 만들때 Date 클래스 및 LocalDateTime 항목이 있는 class 으로 json string 으로 변환하게 되면 변환된 값이 yyyy-MM-dd'T'HH:mm:ss 이러한 포맷으로 변환이 되지 않는다.

 

이런 경우 모두 커스텀을 해줘야 한다.

 

Jackson 

Date with Jackson

public class ItemDate {

  private Integer id; 
  private String name; 
  private String createBy; 
  @JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone="Asia/Seoul") 
  private Date createAt; 
    
}

 

이렇게 하면 "2019-05-15T11:23:10.108+0900" 와 같은 문자열로 변환이된다.

LocalDateTime with Jackson

public class ItemLocalDateTime { 
  private Integer id; 
  private String name; 
  private String createBy; 
  JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS") 
  @JsonDeserialize(using = LocalDateTimeDeserializer.class) 
  @JsonSerialize(using = LocalDateTimeSerializer.class) 
  private LocalDateTime createAt; 
}

이렇게 하면 JSON에서는 "2019-05-15T11:24:46.223" 와 같은 문자열로 변환이된다.

참고로, TimeZone을 생략했는데, Z를 붙이면 Unsupported field: OffsetSeconds 라는 예외가 발생한다.

지역 시간은 시간대 필드를 가지고 있지 않기 때문이다. 이것은 ZoneZonedDateTime에 대응하기 때문이다.

 

Gson

Gson의 경우 어노테이션이 아니라 GsonBuilder 로 타입을 지정해야한다.

registerTypeAdapter 을 통해 커스텀이 가능하므로 class 를 따로 만들어준다.

Date with Gson

class GsonDateConverter implements JsonSerializer<Date>, JsonDeserializer<Date> { 
private static final String FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; 
	@Override 
    public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { 
    	SimpleDateFormat simpleDateFormat = new SimpleDateFormat(FORMAT); return src == null ? null : new JsonPrimitive(simpleDateFormat.format(src)); 
  	} 

	@Override 
    public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 
    	SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); 
        try { return json == null ? null : simpleDateFormat.parse(json.getAsString()); } 
        catch (ParseException e) { throw new JsonParseException(e); } 
    } 
}

LocalDateTime with Gson

public class GsonLocalDateTimeAdapter implements JsonSerializer<LocalDateTime>, JsonDeserializer<LocalDateTime> {
    @Override public JsonElement serialize(LocalDateTime localDateTime, Type srcType, JsonSerializationContext context) {
        return new JsonPrimitive(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(localDateTime));
    }

    @Override public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        return LocalDateTime.parse(json.getAsString(), DateTimeFormatter.ISO_LOCAL_DATE_TIME);
    }
}

사용

Gson gson = new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new GsonLocalDateTimeAdapter())
			.registerTypeAdapter(LocalDate.class, new GsonLocalDateAdapter()).create();

다음처럼  변환하고자 하는 데이터타입 클래스를 어댑터와 같이 registerTypeAdpater 메소드에 추가하면 된다.

 

반응형
반응형

java 에서 파싱을 해주는 라이브러리가 있는데 대표적인 라이브러리고 gson 과 jackson 있다.

gson 을 사용시 기본적으로 new Gson().toJson() 및 fromJson() 으로 기본적인 파싱이 되지만, 종종 커스텀이 필요한 경우가 있다,

이런 경우 Desrializer 및 Serializer 기능이 필요하다.

1. Custom Serialization

Serialization 은 toJson 할 경우 필요하다. 자바 객체를 Json 으로 변환 시 필요하다.

예시

Serialization 생성

public class BooleanSerializer implements JsonSerializer<Boolean> {

    public JsonElement serialize(Boolean aBoolean, Type type,
        JsonSerializationContext jsonSerializationContext) 
    {
        if(aBoolean){
           return new JsonPrimitive(1);
        }
        return new JsonPrimitive(0);
    }
}

사용

    public static void main(String[] args) throws Exception 
    {
        Employee emp = new Employee(1, "Lokesh", "Gupta", "howtodoinjava@gmail.com", true);

        Gson gson = new GsonBuilder()
                .registerTypeAdapter(Boolean.class, new BooleanSerializer())
                .setPrettyPrinting()
                .create();

        String json = gson.toJson(emp);

        System.out.println(json);
    }

결과물

{
  "id": 1,
  "firstName": "Lokesh",
  "lastName": "Gupta",
  "email": "howtodoinjava@gmail.com",
  "active": 1
}

다음과 같이 boolean 이 1또는 0 으로 Json 변환시 사용하려면 다음과 같이 사용하면 된다.

반대로 1또는 0을 true 또는 false 로 변환하려면 JsonDeserializer 을 반대로 implements 를 하면 된다.

이 떄 registerTypeAdapter 메소드는 빌더 타입이라 추가로 클래스를 붙이고 싶은 경우가 있으면 추가하면 된다.

날짜를 gson으로 Serialize 하기

또 사용해야 하는 이유는 날짜 Date 클래스를 gson 으로 Json String 을 만들시

Aug 31, 2020 10:26:17 처럼 날짜가 표시된다. 하지만 2020-08-31 10:26:17 이렇게 변환되기를 바랄것이다.

JsonSerializer<Date> ser = new JsonSerializer<Date>() {
    @Override
    public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext
                context) {
        return src == null ? null : new JsonPrimitive(src.getTime());
    }
};

JsonDeserializer<Date> deser = new JsonDeserializer<Date>() {
    @Override
    public Date deserialize(JsonElement json, Type typeOfT,
                JsonDeserializationContext context) throws JsonParseException {
        return json == null ? null : new Date(json.getAsLong());
    }
};

Gson gson = new GsonBuilder()
                .registerTypeAdapter(Date.class, ser)
                .registerTypeAdapter(Date.class, deser)
                .create();

다음과 같이 익명클래스 로 객체 선언 후 사용 가능 하다.

 

2. Custom Deserialization

반대로 json String 을 객체로 변환시 아래와 같이 사용하면 된다.

json 문자열을 객체로 파싱할때 빈 문자열을 null 로 치환

public class EmptyToNullStringDeserializerGson implements JsonDeserializer<String> {

	@Override
	public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
		String nullCheckString = json.getAsJsonPrimitive().getAsString();
		return "".equals(nullCheckString) ? null : nullCheckString;  
	}

}

사용법

GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapter(String.class, new EmptyToNullStringDeserializerGson());
Gson gson = gb.create();

클래스 response =  gson.fromJson(jsonStr, 클래스명.class);

 

 

참고문헌

https://riptutorial.com/android/example/15339/custom-json-deserializer-using-gson
https://futurestud.io/tutorials/gson-advanced-custom-deserialization-basics
https://howtodoinjava.com/gson/custom-serialization-deserialization/

반응형

+ Recent posts