1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.linecorp.centraldogma.server.internal.api;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Strings.nullToEmpty;
21 import static java.util.Objects.requireNonNull;
22
23 import java.util.Locale;
24 import java.util.concurrent.CompletionStage;
25 import java.util.function.BiFunction;
26 import java.util.function.Function;
27 import java.util.function.Supplier;
28
29 import javax.annotation.Nullable;
30
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import com.fasterxml.jackson.core.JsonProcessingException;
35 import com.fasterxml.jackson.databind.JsonNode;
36 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
37 import com.fasterxml.jackson.databind.node.ObjectNode;
38 import com.google.common.collect.ImmutableList;
39 import com.google.common.collect.ImmutableMap;
40
41 import com.linecorp.armeria.common.HttpResponse;
42 import com.linecorp.armeria.common.HttpStatus;
43 import com.linecorp.armeria.common.MediaType;
44 import com.linecorp.armeria.common.RequestContext;
45 import com.linecorp.armeria.common.logging.LogLevel;
46 import com.linecorp.armeria.common.util.Exceptions;
47 import com.linecorp.armeria.server.HttpResponseException;
48 import com.linecorp.centraldogma.common.ShuttingDownException;
49 import com.linecorp.centraldogma.internal.Jackson;
50
51
52
53
54
55 public final class HttpApiUtil {
56
57 private static final Logger logger = LoggerFactory.getLogger(HttpApiUtil.class);
58 private static final String ERROR_MESSAGE_FORMAT = "{} Returning a {} response: {}";
59
60 static final JsonNode unremoveRequest = Jackson.valueToTree(
61 ImmutableList.of(
62 ImmutableMap.of("op", "replace",
63 "path", "/status",
64 "value", "active")));
65
66
67
68
69
70 public static <T> T throwResponse(RequestContext ctx, HttpStatus status, String message) {
71 throw HttpResponseException.of(newResponse(ctx, status, message));
72 }
73
74
75
76
77
78 public static <T> T throwResponse(RequestContext ctx, HttpStatus status, String format, Object... args) {
79 throw HttpResponseException.of(newResponse(ctx, status, format, args));
80 }
81
82
83
84
85
86 public static <T> T throwResponse(RequestContext ctx, HttpStatus status, Throwable cause, String message) {
87 throw HttpResponseException.of(newResponse(ctx, status, cause, message));
88 }
89
90
91
92
93
94 public static <T> T throwResponse(RequestContext ctx, HttpStatus status, Throwable cause,
95 String format, Object... args) {
96 throw HttpResponseException.of(newResponse(ctx, status, cause, format, args));
97 }
98
99
100
101
102
103 public static HttpResponse newResponse(RequestContext ctx, HttpStatus status,
104 String format, Object... args) {
105 requireNonNull(ctx, "ctx");
106 requireNonNull(status, "status");
107 requireNonNull(format, "format");
108 requireNonNull(args, "args");
109 return newResponse(ctx, status, String.format(Locale.ENGLISH, format, args));
110 }
111
112
113
114
115 public static HttpResponse newResponse(RequestContext ctx, HttpStatus status, String message) {
116 requireNonNull(ctx, "ctx");
117 requireNonNull(status, "status");
118 requireNonNull(message, "message");
119 return newResponse0(ctx, status, null, message);
120 }
121
122
123
124
125 public static HttpResponse newResponse(RequestContext ctx, HttpStatus status, Throwable cause) {
126 requireNonNull(ctx, "ctx");
127 requireNonNull(status, "status");
128 requireNonNull(cause, "cause");
129 return newResponse0(ctx, status, cause, null);
130 }
131
132
133
134
135
136 public static HttpResponse newResponse(RequestContext ctx, HttpStatus status, Throwable cause,
137 String format, Object... args) {
138 requireNonNull(ctx, "ctx");
139 requireNonNull(status, "status");
140 requireNonNull(cause, "cause");
141 requireNonNull(format, "format");
142 requireNonNull(args, "args");
143
144 return newResponse(ctx, status, cause, String.format(Locale.ENGLISH, format, args));
145 }
146
147
148
149
150
151 public static HttpResponse newResponse(RequestContext ctx, HttpStatus status,
152 Throwable cause, String message) {
153 requireNonNull(ctx, "ctx");
154 requireNonNull(status, "status");
155 requireNonNull(cause, "cause");
156 requireNonNull(message, "message");
157
158 return newResponse0(ctx, status, cause, message);
159 }
160
161 private static HttpResponse newResponse0(RequestContext ctx, HttpStatus status,
162 @Nullable Throwable cause, @Nullable String message) {
163 checkArgument(!status.isContentAlwaysEmpty(),
164 "status: %s (expected: a status with non-empty content)", status);
165
166 final ObjectNode node = JsonNodeFactory.instance.objectNode();
167 if (cause != null) {
168 cause = Exceptions.peel(cause);
169 node.put("exception", cause.getClass().getName());
170 if (message == null) {
171 message = cause.getMessage();
172 }
173 }
174
175 final String m = nullToEmpty(message);
176 node.put("message", m);
177
178 final LogLevel logLevel;
179 switch (status.codeClass()) {
180 case SERVER_ERROR:
181 if (cause != null) {
182 if (!(Exceptions.isStreamCancelling(cause) ||
183 cause instanceof ShuttingDownException)) {
184 logLevel = LogLevel.WARN;
185 } else {
186 logLevel = null;
187 }
188 } else {
189 logLevel = LogLevel.WARN;
190 }
191 break;
192 case CLIENT_ERROR:
193 logLevel = LogLevel.DEBUG;
194 break;
195 case UNKNOWN:
196 logLevel = LogLevel.WARN;
197 break;
198 default:
199 logLevel = null;
200 }
201
202
203
204 if (logLevel != null) {
205 if (logLevel == LogLevel.WARN) {
206 if (cause != null) {
207 logger.warn(ERROR_MESSAGE_FORMAT, ctx, status, m, cause);
208 } else {
209 logger.warn(ERROR_MESSAGE_FORMAT, ctx, status, m);
210 }
211 } else {
212 if (cause != null) {
213 logger.debug(ERROR_MESSAGE_FORMAT, ctx, status, m, cause);
214 } else {
215 logger.debug(ERROR_MESSAGE_FORMAT, ctx, status, m);
216 }
217 }
218 }
219
220
221
222 try {
223 return HttpResponse.of(status, MediaType.JSON_UTF_8, Jackson.writeValueAsBytes(node));
224 } catch (JsonProcessingException e) {
225
226 throw new Error(e);
227 }
228 }
229
230
231
232
233
234
235
236 static void checkUnremoveArgument(JsonNode node) {
237 checkArgument(unremoveRequest.equals(node),
238 "Unsupported JSON patch: " + node +
239 " (expected: " + unremoveRequest + ')');
240 }
241
242
243
244
245
246
247 static void checkStatusArgument(String status) {
248 checkArgument("removed".equalsIgnoreCase(status),
249 "invalid status: " + status + " (expected: removed)");
250 }
251
252
253
254
255
256 @SuppressWarnings("unused")
257 static Void throwUnsafelyIfNonNull(@Nullable Object unused,
258 @Nullable Throwable cause) {
259 throwUnsafelyIfNonNull(cause);
260 return null;
261 }
262
263
264
265
266 public static void throwUnsafelyIfNonNull(@Nullable Throwable cause) {
267 if (cause != null) {
268 Exceptions.throwUnsafely(cause);
269 }
270 }
271
272
273
274
275
276 @SuppressWarnings("unchecked")
277 static <T, U> BiFunction<? super T, Throwable, ? extends U> returnOrThrow(Supplier<? super U> supplier) {
278 return (unused, cause) -> {
279 throwUnsafelyIfNonNull(cause);
280 return (U) supplier.get();
281 };
282 }
283
284
285
286
287
288
289 static <T, U> BiFunction<? super T, Throwable, ? extends U> returnOrThrow(
290 Function<? super T, ? extends U> function) {
291 return (result, cause) -> {
292 throwUnsafelyIfNonNull(cause);
293 return function.apply(result);
294 };
295 }
296
297 private HttpApiUtil() {}
298 }