/** * Copyright 2015-2017 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package zipkin.server; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.GZIPInputStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.util.concurrent.SettableListenableFuture; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import zipkin.Codec; import zipkin.collector.Collector; import zipkin.collector.CollectorMetrics; import zipkin.collector.CollectorSampler; import zipkin.internal.Nullable; import zipkin.storage.Callback; import zipkin.storage.StorageComponent; import static org.springframework.web.bind.annotation.RequestMethod.POST; /** * Implements the POST /api/v1/spans endpoint used by instrumentation. */ @RestController @CrossOrigin("${zipkin.query.allowed-origins:*}") public class ZipkinHttpCollector { static final ResponseEntity<?> SUCCESS = ResponseEntity.accepted().build(); static final String APPLICATION_THRIFT = "application/x-thrift"; final CollectorMetrics metrics; final Collector collector; @Autowired ZipkinHttpCollector(StorageComponent storage, CollectorSampler sampler, CollectorMetrics metrics) { this.metrics = metrics.forTransport("http"); this.collector = Collector.builder(getClass()) .storage(storage).sampler(sampler).metrics(this.metrics).build(); } @RequestMapping(value = "/api/v1/spans", method = POST) public ListenableFuture<ResponseEntity<?>> uploadSpansJson( @RequestHeader(value = "Content-Encoding", required = false) String encoding, @RequestBody byte[] body ) { return validateAndStoreSpans(encoding, Codec.JSON, body); } @RequestMapping(value = "/api/v1/spans", method = POST, consumes = APPLICATION_THRIFT) public ListenableFuture<ResponseEntity<?>> uploadSpansThrift( @RequestHeader(value = "Content-Encoding", required = false) String encoding, @RequestBody byte[] body ) { return validateAndStoreSpans(encoding, Codec.THRIFT, body); } ListenableFuture<ResponseEntity<?>> validateAndStoreSpans(String encoding, Codec codec, byte[] body) { SettableListenableFuture<ResponseEntity<?>> result = new SettableListenableFuture<>(); metrics.incrementMessages(); if (encoding != null && encoding.contains("gzip")) { try { body = gunzip(body); } catch (IOException e) { metrics.incrementMessagesDropped(); result.set(ResponseEntity.badRequest().body("Cannot gunzip spans: " + e.getMessage() + "\n")); } } collector.acceptSpans(body, codec, new Callback<Void>() { @Override public void onSuccess(@Nullable Void value) { result.set(SUCCESS); } @Override public void onError(Throwable t) { String message = t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage(); result.set(t.getMessage() == null || message.startsWith("Cannot store") ? ResponseEntity.status(500).body(message + "\n") : ResponseEntity.status(400).body(message + "\n")); } }); return result; } private static final ThreadLocal<byte[]> GZIP_BUFFER = new ThreadLocal<byte[]>() { @Override protected byte[] initialValue() { return new byte[1024]; } }; static byte[] gunzip(byte[] input) throws IOException { GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(input)); try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(input.length)) { byte[] buf = GZIP_BUFFER.get(); int len; while ((len = in.read(buf)) > 0) { outputStream.write(buf, 0, len); } return outputStream.toByteArray(); } } }