blob: 7a5ca8db55515cd4b4dee4acc47a335d38236de3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openmeetings.webservice;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.openmeetings.webservice.util.RateLimited;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
*
* The Service contains methods to trigger a network test
*
*/
@Service("netTestWebService")
@Tag(name = "NetTestService")
@Path("/networktest")
public class NetTestWebService {
private static final Logger log = LoggerFactory.getLogger(NetTestWebService.class);
public enum TestType {
UNKNOWN,
PING,
JITTER,
DOWNLOAD_SPEED,
UPLOAD_SPEED
}
private static final int PING_PACKET_SIZE = 64;
private static final int JITTER_PACKET_SIZE = 1024;
private static final int MAX_DOWNLOAD_SIZE = 5 * 1024 * 1024;
private static final int MAX_UPLOAD_SIZE = 5 * 512 * 1024;
public static final AtomicInteger CLIENT_COUNT = new AtomicInteger();
private static int maxClients = 100;
@PostConstruct
private void report() {
log.debug("MaxClients: {}", maxClients);
}
/**
* Generate a sample for network test
*
* @param type one of ping, jitter, download, upload
* @param inSize requests size of sample
* @return Content as requested
*/
@RateLimited
@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Path("/")
@Operation(
description = "Generate a sample for network test",
responses = {
@ApiResponse(responseCode = "200", description = "Content as requested", content = @Content(mediaType = "application/octet-stream")),
@ApiResponse(responseCode = "400", description = "Unkown type or exceeding Max Upload size"),
@ApiResponse(responseCode = "500", description = "Server error")
}
)
public Response get(
@Parameter(required = true, description = "one of ping, jitter, download, upload") @QueryParam("type") String type
, @Parameter(required = true, description = "requests size of sample") @QueryParam("size") int inSize) {
final int size;
final TestType testType = getTypeByString(type);
log.debug("Network test:: get, {}, {}", testType, inSize);
if (TestType.UNKNOWN == testType) {
return Response.status(Status.BAD_REQUEST).build();
}
// choose data to send
switch (testType) {
case PING:
size = PING_PACKET_SIZE;
break;
case JITTER:
size = JITTER_PACKET_SIZE;
break;
default:
final int count = CLIENT_COUNT.incrementAndGet();
log.info("... download: client count: {}", count);
size = inSize;
break;
}
if (size > MAX_DOWNLOAD_SIZE) {
return Response.status(Status.BAD_REQUEST).build();
}
return Response.ok()
.type(MediaType.APPLICATION_OCTET_STREAM).entity(new InputStream() {
int pos = 0;
@Override
public int read() throws IOException {
pos++;
return pos > size ? -1 : ThreadLocalRandom.current().nextInt(0, 0xFF);
}
@Override
public int available() throws IOException {
return size - pos;
}
@Override
public void close() throws IOException {
if (TestType.DOWNLOAD_SPEED == testType) {
final int count = CLIENT_COUNT.decrementAndGet();
log.info("... close: client count: {}", count);
}
super.close();
}
})
.header("Cache-Control", "no-cache, no-store, no-transform")
.header("Pragma", "no-cache")
.header("Content-Length", String.valueOf(size))
.build();
}
/**
* Upload media to test upload speed
*
* @param size size of file
* @param stream stream of file
* @return Number of bytes uploaded
* @throws IOException
*/
@RateLimited
@POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Path("/")
@Operation(
description = "Upload media to test upload speed",
responses = {
@ApiResponse(responseCode = "200", description = "Number of bytes uploaded"),
@ApiResponse(responseCode = "500", description = "Server error")
}
)
public int upload(
@Parameter(required = true, description = "size") @QueryParam("size") int size
, @Parameter(required = true, description = "stream to upload") InputStream stream) throws IOException {
if (size > MAX_UPLOAD_SIZE) {
return -1;
}
CLIENT_COUNT.incrementAndGet();
byte[] b = new byte[1024];
int totalCount = 0;
int count;
try {
while ((count = stream.read(b)) > -1) {
totalCount += count;
}
log.debug("Total bytes read {}", totalCount);
} finally {
CLIENT_COUNT.decrementAndGet();
}
return totalCount;
}
public static TestType getTypeByString(String typeString) {
if ("ping".equals(typeString)) {
return TestType.PING;
} else if ("jitter".equals(typeString)) {
return TestType.JITTER;
} else if ("download".equals(typeString)) {
return TestType.DOWNLOAD_SPEED;
} else if ("upload".equals(typeString)) {
return TestType.UPLOAD_SPEED;
}
return TestType.UNKNOWN;
}
@Value("${nettest.max.clients}")
private void setMaxClients(int count) {
maxClients = count;
}
public static int getMaxClients() {
return maxClients;
}
}