Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
jittagornp committed Jun 28, 2019
2 parents c7ff97e + 7071d65 commit 19d1574
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 292 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -30,6 +30,7 @@ Spring-boot ใช้ Reactor ซึ่งเป็น lib reactive ตัวน
- [spring-boot-webflux-thymleaf](spring-boot-webflux-thymleaf) - การใช้ Thymleaf ทำ View (Server Site) Rendering (HTML)
- [spring-boot-webflux-filter](spring-boot-webflux-filter) - การเขียน Filter
- [spring-boot-webflux-error-handler](spring-boot-webflux-error-handler) - การจัดการ Exception หรือ Error
- [spring-boot-webflux-custom-error-handler](spring-boot-webflux-custom-error-handler) - การ Custom ตัวจัดการ Exception หรือ Error
- [spring-boot-webflux-validation](spring-boot-webflux-validation) - การ Validate ข้อมูล
- [spring-boot-webflux-custom-validator](spring-boot-webflux-custom-validator) - การ custom validator
- [spring-boot-webflux-scheduling](spring-boot-webflux-scheduling) - การ Run Task แบบ Scheduling
Expand Down
212 changes: 173 additions & 39 deletions spring-boot-webflux-custom-error-handler/README.md
@@ -1,5 +1,5 @@
# spring-boot-webflux-error-handler
ตัวอย่างการเขียน Spring-boot WebFlux Error Handler
# spring-boot-webflux-custom-error-handler
ตัวอย่างการเขียน Spring-boot WebFlux Custom Error Handler

# 1. เพิ่ม Dependencies

Expand Down Expand Up @@ -89,70 +89,204 @@ public class ErrorResponse {
@JsonProperty("error_field")
private List<Field> errorFields;

public List<Field> getErrorFields() {
if (errorFields == null) {
errorFields = new ArrayList<>();
...
}
```
- design ตามนี้้ [https://developer.pamarin.com/document/error/](https://developer.pamarin.com/document/error/)

# 5. เขียน ExceptionHandler
ตัวจัดการ Error แต่ละประเภท

### ประกาศ interface
```java
public interface ErrorResponseExceptionHandler<E extends Throwable> {

Class<E> getTypeClass();

ErrorResponse handle(ServerWebExchange exchange, E e);

}
```
### เขียน Adapter
```java
public abstract class ErrorResponseExceptionHandlerAdapter<E extends Throwable> implements ErrorResponseExceptionHandler<E> {

protected abstract ErrorResponse buildError(ServerWebExchange exchange, E e);

private String getErrorCode(ServerWebExchange exchange) {
return UUID.randomUUID().toString();
}

private ErrorResponse additional(ErrorResponse err, ServerWebExchange exchange, E e) {
ServerHttpRequest httpReq = exchange.getRequest();
err.setState(httpReq.getQueryParams().getFirst("state"));
err.setErrorTimestamp(System.currentTimeMillis());
err.setErrorCode(getErrorCode(exchange));
return err;
}

@Override
public ErrorResponse handle(ServerWebExchange exchange, E e) {
ErrorResponse err = buildError(exchange, e);
return additional(err, exchange, e);
}

}
```
### implementation Error แต่ละประเภท

ตัวจัดการ Exception
```java
@Component
public class ErrorResponseRootExceptionHandler extends ErrorResponseExceptionHandlerAdapter<Exception> {

@Override
public Class<Exception> getTypeClass() {
return Exception.class;
}

@Override
protected ErrorResponse buildError(ServerWebExchange exchange, Exception ex) {
return ErrorResponse.serverError();
}
}
```
ตัวจัดการ ResponseStatusException
```java
@Component
public class ErrorResponseResponseStatusExceptionHandler extends ErrorResponseExceptionHandlerAdapter<ResponseStatusException> {

@Override
public Class<ResponseStatusException> getTypeClass() {
return ResponseStatusException.class;
}

@Override
protected ErrorResponse buildError(ServerWebExchange exchange, ResponseStatusException ex) {
//400
if (ex.getStatus() == HttpStatus.BAD_REQUEST) {
return ErrorResponse.invalidRequest(ex.getMessage());
}
//401
if (ex.getStatus() == HttpStatus.UNAUTHORIZED) {
return ErrorResponse.unauthorized(ex.getMessage());
}
return errorFields;
//403
if (ex.getStatus() == HttpStatus.FORBIDDEN) {
return ErrorResponse.accessDenied(ex.getMessage());
}
//404
if (ex.getStatus() == HttpStatus.NOT_FOUND) {
return ErrorResponse.notFound(ex.getMessage());
}
return ErrorResponse.serverError();
}
}
```
- สามารถเพิ่ม class ตัวจัดการ Exception ใหม่ได้เรื่อย ๆ

# 6. เขียน Resolver
สำหรับ resolve error แต่ละประเภท

ประกาศ interface
```java
public interface ErrorResponseExceptionHandlerResolver {

ErrorResponseExceptionHandler resolve(Throwable e);

}
```

@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Field {
implement interface
```java
@Component
public class DefaultErrorResponseExceptionHandlerResolver implements ErrorResponseExceptionHandlerResolver {

private String name;
private final Map<Class, ErrorResponseExceptionHandler> registry;

private String code;
private final ErrorResponseRootExceptionHandler rootExceptionHandler;

private String description;
@Autowired
public DefaultErrorResponseExceptionHandlerResolver(List<ErrorResponseExceptionHandler> handlers, ErrorResponseRootExceptionHandler rootExceptionHandler) {
this.registry = handlers.stream()
.filter(handler -> handler.getTypeClass() != Exception.class)
.collect(toMap(ErrorResponseExceptionHandler::getTypeClass, handler -> handler));
this.rootExceptionHandler = rootExceptionHandler;
}

@Override
public ErrorResponseExceptionHandler resolve(Throwable e) {
ErrorResponseExceptionHandler handler = registry.get(e.getClass());
if (handler == null) {
return rootExceptionHandler;
}
return handler;
}

}
```
- design ตามนี้้ [https://developer.pamarin.com/document/error/](https://developer.pamarin.com/document/error/)

# 5. เขียน Controller Advice
เป็น Global Exception handler
# 7. เขียน WebExceptionHandler
เป็นตัวจัดการ Global Exception ทุกประเภท ซึ่ง WebFlux จะโยน Exception เข้ามาที่นี่
```java
@ControllerAdvice
public class ErrorControllerAdvice {

@ExceptionHandler(Exception.class)
public Mono<ResponseEntity<ErrorResponse>> handle(Exception ex, ServerWebExchange exchange) {
return Mono.just(
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(
ErrorResponse.builder()
.error("server_error")
.errorDescription("Internal Server Error")
.errorStatus(HttpStatus.INTERNAL_SERVER_ERROR.value())
.errorTimestamp(System.currentTimeMillis())
.errorUri("https://developer.pamarin.com/document/error/")
.errorCode(UUID.randomUUID().toString())
.build()
)
@Slf4j
@Component
@Order(-2)
public class GlobalWebExceptionHandler implements WebExceptionHandler {

private final ObjectMapper objectMapper;

private final ErrorResponseExceptionHandlerResolver resolver;

@Autowired
public GlobalWebExceptionHandler(ObjectMapper objectMapper, ErrorResponseExceptionHandlerResolver resolver) {
this.resolver = resolver;
this.objectMapper = objectMapper;
}

@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable e) {
log.debug("Throwable class => {}", e.getClass().getName());
ErrorResponse error = resolver.resolve(e).handle(exchange, e);
error.setErrorUri("https://developer.pamarin.com/document/error/");
return jsonResponse(
ResponseEntity.status(HttpStatus.valueOf(error.getErrorStatus()))
.body(error),
exchange
);
}

public Mono<Void> jsonResponse(
final ResponseEntity<ErrorResponse> entity,
final ServerWebExchange exchange
) {
final ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(entity.getStatusCode());
response.getHeaders().addAll(entity.getHeaders());
response.getHeaders().put(HttpHeaders.CONTENT_TYPE, Arrays.asList(MediaType.APPLICATION_JSON_UTF8_VALUE));
try {
final DataBuffer buffer = response.bufferFactory().wrap(objectMapper.writeValueAsBytes(entity.getBody()));
return response.writeWith(Mono.just(buffer)).doOnError(error -> DataBufferUtils.release(buffer));
} catch (final JsonProcessingException ex) {
return Mono.error(ex);
}
}
}

```
- เราสามารถ เพิ่ม method เพื่อ catch exception อื่น ๆ ได้

# 6. Build
# 8. Build
cd ไปที่ root ของ project จากนั้น
``` shell
$ mvn clean install
```

# 7. Run
# 9. Run
``` shell
$ mvn spring-boot:run
```

# 8. เข้าใช้งาน
# 10. เข้าใช้งาน

เปิด browser แล้วเข้า [http://localhost:8080](http://localhost:8080)

Expand Down
106 changes: 7 additions & 99 deletions spring-boot-webflux-custom-validator/README.md
Expand Up @@ -131,118 +131,26 @@ public class RegisterController {
```
- สังเกตว่าตรง input method ใน controller มี @Validated เพื่อบอกว่าให้ validate input ที่เป็น request body (json) ด้วย

# 6. เขียน error model
```java
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ErrorResponse {

private String error;

@JsonProperty("error_status")
private int errorStatus;

@JsonProperty("error_description")
private String errorDescription;

@JsonProperty("error_timestamp")
private long errorTimestamp;

@JsonProperty("error_uri")
private String errorUri;

@JsonProperty("error_code")
private String errorCode;

private String state;

@JsonProperty("error_field")
private List<Field> errorFields;

public List<Field> getErrorFields() {
if (errorFields == null) {
errorFields = new ArrayList<>();
}
return errorFields;
}

@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Field {

private String name;

private String code;

private String description;

}
}
```

อ้างอิง error : [https://developer.pamarin.com/document/error/](https://developer.pamarin.com/document/error/)

# 7. เขียน Controller Advice
เพื่อแปลง Error ไปเป็น format ตามที่เราต้องการ
```java
@ControllerAdvice
public class ErrorControllerAdvice {

@ExceptionHandler(WebExchangeBindException.class)
public Mono<ResponseEntity<ErrorResponse>> validationError(WebExchangeBindException ex, ServerWebExchange exchange) {
return Mono.just(
ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(
ErrorResponse.builder()
.error("bad_request")
.errorDescription("Validate fail")
.errorStatus(HttpStatus.BAD_REQUEST.value())
.errorTimestamp(System.currentTimeMillis())
.errorUri("https://developer.pamarin.com/document/error/")
.errorCode(UUID.randomUUID().toString())
.errorFields(
ex.getFieldErrors()
.stream()
.map(f -> {
return ErrorResponse.Field.builder()
.name(f.getField())
.code(f.getCode())
.description(f.getDefaultMessage())
.build();
})
.collect(toList())
)
.build()
)
);
}

...
}
```
# 6. เขียน exception handler

# 8. Build
เหมือนหัว [spring-boot-webflux-validation](../spring-boot-webflux-validation)

# 7. Build
cd ไปที่ root ของ project จากนั้น
``` shell
$ mvn clean install
```

# 9. Run
# 8. Run
``` shell
$ mvn spring-boot:run
```

# 10. เข้าใช้งาน
# 9. เข้าใช้งาน

เปิด browser แล้วเข้า [http://localhost:8080](http://localhost:8080)

# 11. ลองยิง request ทดสอบผ่าน postman
# 10. ลองยิง request ทดสอบผ่าน postman
> POST : http://localhost:8080/register
ได้ผลลัพธ์
Expand Down

0 comments on commit 19d1574

Please sign in to comment.