How do I add custom SSL certificate validation to osmdroid's MapTileDownloader? - java

I'm trying to load map tiles from an internal SSL server. The SSL certificate's root of trust is not recognized by the Android system.
W/o*.o*.t*.m*.MapTileDow*(2837): IOException downloading MapTile: /8/37/4 :
javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
I'm already familiar with the problem and have solved it in the rest of the application based on this excellent SO answer. Essentially, I extended my own SSLSocketFactory and X509TrustManager which load my SSL certificate's root of trust from a .bks file bundled with the app. To create a secure connection, I call ((HttpsURLConnection) connection).setSSLSocketFactory(mySSLSocketFactory) and the certificate is verified using my classes with my root of trust.
My question is how do I do the same thing for osmdroid? I'm creating my own XYTileSource where I set the URL, file extension, size, etc. of my map tiles. I see that osmdroid creates its connections to download map tile images in MapTileDownloader. I can write my own replacement class that will address the SSL issue in the same manner, but how do I tell osmdroid to use my custom downloader instead of the default?

It turns out this is possible without changing the source of osmdroid, due to the public MapView(Context context, int tileSizePixels, ResourceProxy resourceProxy, MapTileProviderBase aTileProvider) constrtuctor.
Assuming you already have a custom class like MySSLSocketFactory (which extends javax.net.ssl.SSLSocketFactory), the basic process looks like this:
Create a drop-in replacement class for MapTileDownloader to perform the download in a way that makes use of MySSLSocketFactory. Let's call this MyTileDownloader.
Create a drop-in replacement class for MapTileProviderBasic that instantiates your custom MyTileDownloader. Let's call this MyTileProvider.
Instantiate your tile source as a new XYTileSource (no need to write a custom class).
Instantiate MyTileProvider with your tile source instance.
Instantiate MapVew with your tile provider instance.
MySSLSocketFactory is left as an exercise for the reader. See this post.
MyTileDownloader looks something like this:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.UnknownHostException;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import org.osmdroid.tileprovider.MapTile;
import org.osmdroid.tileprovider.MapTileRequestState;
import org.osmdroid.tileprovider.modules.IFilesystemCache;
import org.osmdroid.tileprovider.modules.INetworkAvailablityCheck;
import org.osmdroid.tileprovider.modules.MapTileDownloader;
import org.osmdroid.tileprovider.modules.MapTileModuleProviderBase;
import org.osmdroid.tileprovider.tilesource.BitmapTileSourceBase.LowMemoryException;
import org.osmdroid.tileprovider.tilesource.ITileSource;
import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase;
import org.osmdroid.tileprovider.util.StreamUtils;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.Log;
/**
* A drop-in replacement for {#link MapTileDownloader}. This loads tiles from an
* HTTP or HTTPS server, making use of a custom {#link SSLSocketFactory} for SSL
* peer verification.
*/
public class MyTileDownloader extends MapTileModuleProviderBase {
private static final String TAG = "MyMapTileDownloader";
protected OnlineTileSourceBase mTileSource;
protected final IFilesystemCache mFilesystemCache;
protected final INetworkAvailablityCheck mNetworkAvailablityCheck;
protected final SSLSocketFactory mSSLSocketFactory;
public MyTileDownloader(ITileSource pTileSource,
IFilesystemCache pFilesystemCache,
INetworkAvailablityCheck pNetworkAvailablityCheck,
SSLSocketFactory pSSLSocketFactory) {
super(4, TILE_DOWNLOAD_MAXIMUM_QUEUE_SIZE);
setTileSource(pTileSource);
mFilesystemCache = pFilesystemCache;
mNetworkAvailablityCheck = pNetworkAvailablityCheck;
mSSLSocketFactory = pSSLSocketFactory;
}
public ITileSource getTileSource() {
return mTileSource;
}
#Override
public void setTileSource(final ITileSource tileSource) {
// We are only interested in OnlineTileSourceBase tile sources
if (tileSource instanceof OnlineTileSourceBase)
mTileSource = (OnlineTileSourceBase) tileSource;
else
mTileSource = null;
}
#Override
public boolean getUsesDataConnection() {
return true;
}
#Override
protected String getName() {
return "Online Tile Download Provider";
}
#Override
protected String getThreadGroupName() {
return "downloader";
}
#Override
public int getMinimumZoomLevel() {
return (mTileSource != null ? mTileSource.getMinimumZoomLevel()
: MINIMUM_ZOOMLEVEL);
}
#Override
public int getMaximumZoomLevel() {
return (mTileSource != null ? mTileSource.getMaximumZoomLevel()
: MAXIMUM_ZOOMLEVEL);
}
#Override
protected Runnable getTileLoader() {
return new TileLoader();
};
private class TileLoader extends MapTileModuleProviderBase.TileLoader {
#Override
public Drawable loadTile(final MapTileRequestState aState)
throws CantContinueException {
if (mTileSource == null)
return null;
InputStream in = null;
OutputStream out = null;
final MapTile tile = aState.getMapTile();
try {
if (mNetworkAvailablityCheck != null
&& !mNetworkAvailablityCheck.getNetworkAvailable()) {
if (DEBUGMODE)
Log.d(TAG, "Skipping " + getName()
+ " due to NetworkAvailabliltyCheck.");
return null;
}
final String tileURLString = mTileSource.getTileURLString(tile);
if (DEBUGMODE)
Log.d(TAG, "Downloading Maptile from url: " + tileURLString);
if (TextUtils.isEmpty(tileURLString))
return null;
// Create an HttpURLConnection to download the tile
URL url = new URL(tileURLString);
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
connection.setConnectTimeout(30000);
connection.setReadTimeout(30000);
// Use our custom SSLSocketFactory for secure connections
if ("https".equalsIgnoreCase(url.getProtocol()))
((HttpsURLConnection) connection)
.setSSLSocketFactory(mSSLSocketFactory);
// Open the input stream
in = new BufferedInputStream(connection.getInputStream(),
StreamUtils.IO_BUFFER_SIZE);
// Check to see if we got success
if (connection.getResponseCode() != 200) {
Log.w(TAG, "Problem downloading MapTile: " + tile
+ " HTTP response: " + connection.getHeaderField(0));
return null;
}
// Read the tile into an in-memory byte array
final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
out = new BufferedOutputStream(dataStream,
StreamUtils.IO_BUFFER_SIZE);
StreamUtils.copy(in, out);
out.flush();
final byte[] data = dataStream.toByteArray();
final ByteArrayInputStream byteStream = new ByteArrayInputStream(
data);
// Save the data to the filesystem cache
if (mFilesystemCache != null) {
mFilesystemCache.saveFile(mTileSource, tile, byteStream);
byteStream.reset();
}
final Drawable result = mTileSource.getDrawable(byteStream);
return result;
} catch (final UnknownHostException e) {
Log.w(TAG, "UnknownHostException downloading MapTile: " + tile
+ " : " + e);
throw new CantContinueException(e);
} catch (final LowMemoryException e) {
Log.w(TAG, "LowMemoryException downloading MapTile: " + tile
+ " : " + e);
throw new CantContinueException(e);
} catch (final FileNotFoundException e) {
Log.w(TAG, "Tile not found: " + tile + " : " + e);
} catch (final IOException e) {
Log.w(TAG, "IOException downloading MapTile: " + tile + " : "
+ e);
} catch (final Throwable e) {
Log.e(TAG, "Error downloading MapTile: " + tile, e);
} finally {
StreamUtils.closeStream(in);
StreamUtils.closeStream(out);
}
return null;
}
#Override
protected void tileLoaded(final MapTileRequestState pState,
final Drawable pDrawable) {
// Don't return the tile Drawable because we'll wait for the fs
// provider to ask for it. This prevent flickering when a load
// of delayed downloads complete for tiles that we might not
// even be interested in any more.
super.tileLoaded(pState, null);
}
}
}
MyTileProvider looks something like this.
Note that you'll need a way to get access to your instance of MySSLSocketFactory inside this class. This is left as an exercise for the reader. I did this using app.getSSLSocketFactory(), where app is an instance of a custom class that extends Application, but your mileage may vary.
import javax.net.ssl.SSLSocketFactory;
import org.osmdroid.tileprovider.IMapTileProviderCallback;
import org.osmdroid.tileprovider.IRegisterReceiver;
import org.osmdroid.tileprovider.MapTileProviderArray;
import org.osmdroid.tileprovider.MapTileProviderBasic;
import org.osmdroid.tileprovider.modules.INetworkAvailablityCheck;
import org.osmdroid.tileprovider.modules.MapTileFileArchiveProvider;
import org.osmdroid.tileprovider.modules.MapTileFilesystemProvider;
import org.osmdroid.tileprovider.modules.NetworkAvailabliltyCheck;
import org.osmdroid.tileprovider.modules.TileWriter;
import org.osmdroid.tileprovider.tilesource.ITileSource;
import org.osmdroid.tileprovider.util.SimpleRegisterReceiver;
import android.content.Context;
/**
* A drop-in replacement for {#link MapTileProviderBasic}. This top-level tile
* provider implements a basic tile request chain which includes a
* {#link MapTileFilesystemProvider} (a file-system cache), a
* {#link MapTileFileArchiveProvider} (archive provider), and a
* {#link MyTileDownloader} (downloads map tiles via tile source).
*/
public class MyTileProvider extends MapTileProviderArray implements
IMapTileProviderCallback {
public MyTileProvider(final Context pContext, final ITileSource pTileSource) {
this(new SimpleRegisterReceiver(pContext),
new NetworkAvailabliltyCheck(pContext), pTileSource, app
.getSSLSocketFactory());
}
protected MyTileProvider(final IRegisterReceiver pRegisterReceiver,
final INetworkAvailablityCheck aNetworkAvailablityCheck,
final ITileSource pTileSource,
final SSLSocketFactory pSSLSocketFactory) {
super(pTileSource, pRegisterReceiver);
// Look for raw tiles on the file system
final MapTileFilesystemProvider fileSystemProvider = new MapTileFilesystemProvider(
pRegisterReceiver, pTileSource);
mTileProviderList.add(fileSystemProvider);
// Look for tile archives on the file system
final MapTileFileArchiveProvider archiveProvider = new MapTileFileArchiveProvider(
pRegisterReceiver, pTileSource);
mTileProviderList.add(archiveProvider);
// Look for raw tiles on the Internet
final TileWriter tileWriter = new TileWriter();
final MyTileDownloader downloaderProvider = new MyTileDownloader(
pTileSource, tileWriter, aNetworkAvailablityCheck,
pSSLSocketFactory);
mTileProviderList.add(downloaderProvider);
}
}
Finally, the instantiation looks something like this:
XYTileSource tileSource = new XYTileSource("MapQuest", null, 3, 8, 256, ".jpg",
"https://10.0.0.1/path/to/your/map/tiles/");
MapTileProviderBase tileProvider = new MyTileProvider(context, tileSource);
ResourceProxy resourceProxy = new DefaultResourceProxyImpl(context);
MapView mapView = new MapView(context, 256, resourceProxy, tileProvider);

I don't use osmdroid, but unless it has public interface to replace the downloader class(es), your best bet is to get the source and patch it to make it configurable or use your own downloader class. If MapTileDownloader implements some interface you could probably do some reflection voodoo to replace it at runtime, but that might have unknown side effects.

Related

Getting partial json response for s3select with aws java sdk v2

I am trying to implement s3select in a spring boot app to query parquet file in s3 bucket, I am only getting partial result from the s3select output, Please help to identify the issue, i have used aws java sdk v2.
Upon checking the json output(printed in the console), overall characters in the output is 65k.
I am using eclipse and tried unchecking "Limit console output" in the console preference, which did not help.
Code is here:-
import java.util.List;
import java.util.concurrent.CompletableFuture;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.async.SdkPublisher;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.CompressionType;
import software.amazon.awssdk.services.s3.model.EndEvent;
import software.amazon.awssdk.services.s3.model.ExpressionType;
import software.amazon.awssdk.services.s3.model.InputSerialization;
import software.amazon.awssdk.services.s3.model.JSONOutput;
import software.amazon.awssdk.services.s3.model.OutputSerialization;
import software.amazon.awssdk.services.s3.model.ParquetInput;
import software.amazon.awssdk.services.s3.model.RecordsEvent;
import software.amazon.awssdk.services.s3.model.SelectObjectContentEventStream;
import software.amazon.awssdk.services.s3.model.SelectObjectContentEventStream.EventType;
import software.amazon.awssdk.services.s3.model.SelectObjectContentRequest;
import software.amazon.awssdk.services.s3.model.SelectObjectContentResponse;
import software.amazon.awssdk.services.s3.model.SelectObjectContentResponseHandler;
public class ParquetSelect {
private static final String BUCKET_NAME = "<bucket-name>";
private static final String KEY = "<object-key>";
private static final String QUERY = "select * from S3Object s";
public static S3AsyncClient s3;
public static void selectObjectContent() {
Handler handler = new Handler();
SelectQueryWithHandler(handler).join();
RecordsEvent recordsEvent = (RecordsEvent) handler.receivedEvents.stream()
.filter(e -> e.sdkEventType() == EventType.RECORDS)
.findFirst()
.orElse(null);
System.out.println(recordsEvent.payload().asUtf8String());
}
private static CompletableFuture<Void> SelectQueryWithHandler(SelectObjectContentResponseHandler handler) {
InputSerialization inputSerialization = InputSerialization.builder()
.parquet(ParquetInput.builder().build())
.compressionType(CompressionType.NONE)
.build();
OutputSerialization outputSerialization = OutputSerialization.builder()
.json(JSONOutput.builder().build())
.build();
SelectObjectContentRequest select = SelectObjectContentRequest.builder()
.bucket(BUCKET_NAME)
.key(KEY)
.expression(QUERY)
.expressionType(ExpressionType.SQL)
.inputSerialization(inputSerialization)
.outputSerialization(outputSerialization)
.build();
return s3.selectObjectContent(select, handler);
}
private static class Handler implements SelectObjectContentResponseHandler {
private SelectObjectContentResponse response;
private List<SelectObjectContentEventStream> receivedEvents = new ArrayList<>();
private Throwable exception;
#Override
public void responseReceived(SelectObjectContentResponse response) {
this.response = response;
}
#Override
public void onEventStream(SdkPublisher<SelectObjectContentEventStream> publisher) {
publisher.subscribe(receivedEvents::add);
}
#Override
public void exceptionOccurred(Throwable throwable) {
exception = throwable;
}
#Override
public void complete() {
}
}
}
I see you are using selectObjectContent(). Have you tried calling the s3AsyncClient.getObject() method. Does that work for you?
For example, here is a code example that gets a PDF file from an Amazon S3 bucket and write the PDF file to a local file.
package com.example.s3.async;
// snippet-start:[s3.java2.async_stream_ops.complete]
// snippet-start:[s3.java2.async_stream_ops.import]
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture;
// snippet-end:[s3.java2.async_stream_ops.import]
// snippet-start:[s3.java2.async_stream_ops.main]
/**
* Before running this Java V2 code example, set up your development environment, including your credentials.
*
* For more information, see the following documentation topic:
*
* https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
*/
public class S3AsyncStreamOps {
public static void main(String[] args) {
final String usage = "\n" +
"Usage:\n" +
" <bucketName> <objectKey> <path>\n\n" +
"Where:\n" +
" bucketName - The name of the Amazon S3 bucket (for example, bucket1). \n\n" +
" objectKey - The name of the object (for example, book.pdf). \n" +
" path - The local path to the file (for example, C:/AWS/book.pdf). \n" ;
if (args.length != 3) {
System.out.println(usage);
System.exit(1);
}
String bucketName = args[0];
String objectKey = args[1];
String path = args[2];
ProfileCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create();
Region region = Region.US_EAST_1;
S3AsyncClient s3AsyncClient = S3AsyncClient.builder()
.region(region)
.credentialsProvider(credentialsProvider)
.build();
GetObjectRequest objectRequest = GetObjectRequest.builder()
.bucket(bucketName)
.key(objectKey)
.build();
CompletableFuture<GetObjectResponse> futureGet = s3AsyncClient.getObject(objectRequest,
AsyncResponseTransformer.toFile(Paths.get(path)));
futureGet.whenComplete((resp, err) -> {
try {
if (resp != null) {
System.out.println("Object downloaded. Details: "+resp);
} else {
err.printStackTrace();
}
} finally {
// Only close the client when you are completely done with it.
s3AsyncClient.close();
}
});
futureGet.join();
}
}

Amazon Rekognition label detection problem

Problem:
I'm starting to use Amazon Rekognition label detection, the problem is that I don't know how to pass a url to the DetectLabelsRequest () object. That url contains an image which is the one I need to analyze.
Code:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
instantiateRekognition()
}
private fun instantiateRekognition(){
val rekognitionClient: AmazonRekognition =
AmazonRekognitionClient(BasicAWSCredentials("", ""))
val sourceStream: InputStream = FileInputStream(
"http://placehold.it/120x120&text=image1")
var souImage: Image = Image()
val byteBuffer = ByteBuffer.allocate(sourceStream.toString().length)
souImage.withBytes(byteBuffer)
val request = DetectLabelsRequest().withImage(souImage)
.withMaxLabels(10)
.withMinConfidence(75f)
try {
val result = rekognitionClient.detectLabels(request)
val labels = result.labels
for (label in labels) {
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
URL of image to analyze:
http://placehold.it/120x120&text=image1
Here is your solution using the Amazon Rekognition Java V2 API.
If you are not familiar with V2, please refer to this AWS Java Dev Guide topic:
Get started with the AWS SDK for Java 2.x
Code:
package com.example.rekognition;
// snippet-start:[rekognition.java2.detect_labels.import]
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.rekognition.RekognitionClient;
import software.amazon.awssdk.services.rekognition.model.Image;
import software.amazon.awssdk.services.rekognition.model.DetectLabelsRequest;
import software.amazon.awssdk.services.rekognition.model.DetectLabelsResponse;
import software.amazon.awssdk.services.rekognition.model.Label;
import software.amazon.awssdk.services.rekognition.model.RekognitionException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
// snippet-end:[rekognition.java2.detect_labels.import]
/**
* To run this Java V2 code example, ensure that you have setup your development environment, including your credentials.
*
* For information, see this documentation topic:
*
* https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
*/
public class DetectLabels {
public static void main(String[] args) {
final String USAGE = "\n" +
"Usage: " +
"DetectLabels <sourceImage>\n\n" +
"Where:\n" +
"sourceImage - the path to the image (for example, C:\\AWS\\pic1.png). \n\n";
// if (args.length != 1) {
// System.out.println(USAGE);
// System.exit(1);
// }
String sourceImage = "C:\\Users\\scmacdon\\lake.png" ; // args[0] ;
Region region = Region.US_EAST_1;
RekognitionClient rekClient = RekognitionClient.builder()
.region(region)
.build();
detectImageLabels(rekClient, sourceImage );
rekClient.close();
}
// snippet-start:[rekognition.java2.detect_labels.main]
public static void detectImageLabels(RekognitionClient rekClient, String sourceImage) {
try {
InputStream sourceStream = new URL("http://placehold.it/120x120&text=image1").openStream();
// InputStream sourceStream = new FileInputStream(sourceImage);
SdkBytes sourceBytes = SdkBytes.fromInputStream(sourceStream);
// Create an Image object for the source image.
Image souImage = Image.builder()
.bytes(sourceBytes)
.build();
DetectLabelsRequest detectLabelsRequest = DetectLabelsRequest.builder()
.image(souImage)
.maxLabels(10)
.build();
DetectLabelsResponse labelsResponse = rekClient.detectLabels(detectLabelsRequest);
List<Label> labels = labelsResponse.labels();
System.out.println("Detected labels for the given photo");
for (Label label: labels) {
System.out.println(label.name() + ": " + label.confidence().toString());
}
} catch (RekognitionException | FileNotFoundException | MalformedURLException e) {
System.out.println(e.getMessage());
System.exit(1);
} catch (IOException e) {
e.printStackTrace();
}
}
// snippet-end:[rekognition.java2.detect_labels.main]
}
Results given your image at the given URL:
I tested with the URL TOO
https://images.unsplash.com/photo-1557456170-0cf4f4d0d362?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8bGFrZXxlbnwwfHwwfHw%3D&ixlib=rb-1.2.1&w=1000&q=80
This produced:
You can find many other Java V2 Amazon Rekognition examples here:
https://github.com/awsdocs/aws-doc-sdk-examples/tree/master/javav2/example_code/rekognition

Grizzly Http Server - accepting only one connection at a time

I have a Grizzly Http Server with Async processing added. It is queuing my requests and processing only one request at a time, despite adding async support to it.
Path HttpHandler was bound to is: "/"
Port number: 7777
Behavior observed when I hit http://localhost:7777 from two browsers simultaneously is:
Second call waits till first one is completed. I want my second http call also to work simultaneously in tandom with first http call.
EDIT Github link of my project
Here are the classes
GrizzlyMain.java
package com.grizzly;
import java.io.IOException;
import java.net.URI;
import javax.ws.rs.core.UriBuilder;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
import org.glassfish.grizzly.strategies.WorkerThreadIOStrategy;
import org.glassfish.grizzly.threadpool.ThreadPoolConfig;
import com.grizzly.http.IHttpHandler;
import com.grizzly.http.IHttpServerFactory;
public class GrizzlyMain {
private static HttpServer httpServer;
private static void startHttpServer(int port) throws IOException {
URI uri = getBaseURI(port);
httpServer = IHttpServerFactory.createHttpServer(uri,
new IHttpHandler(null));
TCPNIOTransport transport = getListener(httpServer).getTransport();
ThreadPoolConfig config = ThreadPoolConfig.defaultConfig()
.setPoolName("worker-thread-").setCorePoolSize(6).setMaxPoolSize(6)
.setQueueLimit(-1)/* same as default */;
transport.configureBlocking(false);
transport.setSelectorRunnersCount(3);
transport.setWorkerThreadPoolConfig(config);
transport.setIOStrategy(WorkerThreadIOStrategy.getInstance());
transport.setTcpNoDelay(true);
System.out.println("Blocking Transport(T/F): " + transport.isBlocking());
System.out.println("Num SelectorRunners: "
+ transport.getSelectorRunnersCount());
System.out.println("Num WorkerThreads: "
+ transport.getWorkerThreadPoolConfig().getCorePoolSize());
httpServer.start();
System.out.println("Server Started #" + uri.toString());
}
public static void main(String[] args) throws InterruptedException,
IOException, InstantiationException, IllegalAccessException,
ClassNotFoundException {
startHttpServer(7777);
System.out.println("Press any key to stop the server...");
System.in.read();
}
private static NetworkListener getListener(HttpServer httpServer) {
return httpServer.getListeners().iterator().next();
}
private static URI getBaseURI(int port) {
return UriBuilder.fromUri("https://0.0.0.0/").port(port).build();
}
}
HttpHandler (with async support built in)
package com.grizzly.http;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import javax.ws.rs.core.Application;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.http.server.Response;
import org.glassfish.grizzly.http.util.HttpStatus;
import org.glassfish.grizzly.threadpool.GrizzlyExecutorService;
import org.glassfish.grizzly.threadpool.ThreadPoolConfig;
import org.glassfish.jersey.server.ApplicationHandler;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.spi.Container;
import com.grizzly.Utils;
/**
* Jersey {#code Container} implementation based on Grizzly
* {#link org.glassfish.grizzly.http.server.HttpHandler}.
*
* #author Jakub Podlesak (jakub.podlesak at oracle.com)
* #author Libor Kramolis (libor.kramolis at oracle.com)
* #author Marek Potociar (marek.potociar at oracle.com)
*/
public final class IHttpHandler extends HttpHandler implements Container {
private static int reqNum = 0;
final ExecutorService executorService = GrizzlyExecutorService
.createInstance(ThreadPoolConfig.defaultConfig().copy()
.setCorePoolSize(4).setMaxPoolSize(4));
private volatile ApplicationHandler appHandler;
/**
* Create a new Grizzly HTTP container.
*
* #param application
* JAX-RS / Jersey application to be deployed on Grizzly HTTP
* container.
*/
public IHttpHandler(final Application application) {
}
#Override
public void start() {
super.start();
}
#Override
public void service(final Request request, final Response response) {
System.out.println("\nREQ_ID: " + reqNum++);
System.out.println("THREAD_ID: " + Utils.getThreadName());
response.suspend();
// Instruct Grizzly to not flush response, once we exit service(...) method
executorService.execute(new Runnable() {
#Override
public void run() {
try {
System.out.println("Executor Service Current THREAD_ID: "
+ Utils.getThreadName());
Thread.sleep(25 * 1000);
} catch (Exception e) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
} finally {
String content = updateResponse(response);
System.out.println("Response resumed > " + content);
response.resume();
}
}
});
}
#Override
public ApplicationHandler getApplicationHandler() {
return appHandler;
}
#Override
public void destroy() {
super.destroy();
appHandler = null;
}
// Auto-generated stuff
#Override
public ResourceConfig getConfiguration() {
return null;
}
#Override
public void reload() {
}
#Override
public void reload(ResourceConfig configuration) {
}
private String updateResponse(final Response response) {
String data = null;
try {
data = new Date().toLocaleString();
response.getWriter().write(data);
} catch (IOException e) {
data = "Unknown error from our server";
response.setStatus(500, data);
}
return data;
}
}
IHttpServerFactory.java
package com.grizzly.http;
import java.net.URI;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.http.server.ServerConfiguration;
/**
* #author smc
*/
public class IHttpServerFactory {
private static final int DEFAULT_HTTP_PORT = 80;
public static HttpServer createHttpServer(URI uri, IHttpHandler handler) {
final String host = uri.getHost() == null ? NetworkListener.DEFAULT_NETWORK_HOST
: uri.getHost();
final int port = uri.getPort() == -1 ? DEFAULT_HTTP_PORT : uri.getPort();
final NetworkListener listener = new NetworkListener("IGrizzly", host, port);
listener.setSecure(false);
final HttpServer server = new HttpServer();
server.addListener(listener);
final ServerConfiguration config = server.getServerConfiguration();
if (handler != null) {
config.addHttpHandler(handler, uri.getPath());
}
config.setPassTraceRequest(true);
return server;
}
}
It seems the problem is the browser waiting for the first request to complete, and thus more a client-side than a server-side issue. It disappears if you test with two different browser processes, or even if you open two distinct paths (let's say localhost:7777/foo and localhost:7777/bar) in the same browser process (note: the query string partecipates in making up the path in the HTTP request line).
How I understood it
Connections in HTTP/1.1 are persistent by default, ie browsers recycle the same TCP connection over and over again to speed things up. However, this doesn't mean that all requests to the same domain will be serialized: in fact, a connection pool is allocated on a per-hostname basis (source). Unfortunately, requests with the same path are effectively enqueued (at least on Firefox and Chrome) - I guess it's a device that browsers employ to protect server resources (and thus user experience)
Real-word applications don't suffer from this because different resources are deployed to different URLs.
DISCLAIMER: I wrote this answer based on my observations and some educated guess. I think things may actually be like this, however a tool like Wireshark should be used to follow the TCP stream and definitely assert this is what happens.

Map a image file through Spring controller

Is there any way to map a image file using a spring controller? In my spring application, I want store the images in the directory src/main/resources (i'm using maven) and access them with a method like this:
#RequestMapping(value="image/{theString}")
public ModelAndView image(#PathVariable String theString) {
return new ModelAndView('what should be placed here?');
}
the string theString it's the image name (without extension). With this approach, I should be able to access my images this way:
/webapp/controller_mapping/image/image_name
Anyone can point a direction to do that?
You can return HttpEntity<byte[]>. Construct new instance providing image byte array and necessary headers like content length and mime type then return it from your method. Image bytes can be obtained using classloader getResourceAsStream method.
This works for me. It could use some cleaning up but it works. The ServiceException is just a simple base exception.
Good Luck!
package com.dhargis.example;
import java.io.File;
import java.io.IOException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#Controller
#RequestMapping("/image")
public class ImageController {
private static final Logger log = Logger.getLogger(ImageController.class);
private String filestore = "C:\\Users\\dhargis";
//produces = "application/octet-stream"
#RequestMapping(value = "/{filename:.+}", method = RequestMethod.GET)
public void get( #PathVariable String filename,
HttpServletRequest request,
HttpServletResponse response) {
log.info("Getting file " + filename);
try {
byte[] content = null;
File store = new File(filestore);
if( store.exists() ){
File file = new File(store.getPath()+File.separator+filename);
if( file.exists() ){
content = FileUtils.readFileToByteArray(file);
} else {
throw new ServiceException("File does not exist");
}
} else {
throw new ServiceException("Report store is required");
}
ServletOutputStream out = response.getOutputStream();
out.write(content);
out.flush();
out.close();
} catch (ServiceException e) {
log.error("Error on get", e);
} catch (IOException e) {
log.error("Error on get", e);
}
}
}
<!-- begin snippet: js hide: false -->

Enable logging for JDK class programmatically

Ok, the case is simple. I need to be able to enable/disable logging for a JDK class (HttpURLConnection) programmatically.
public class HttpLoggingTest {
/**
Just a dummy to get some action from HttpURLConnection
*/
private static void getSomething(String urlStr) throws MalformedURLException, IOException {
System.out.println("----- " + urlStr);
HttpURLConnection conn = (HttpURLConnection) new URL("http://www.google.com").openConnection();
for (Entry<String, List<String>> header : conn.getHeaderFields().entrySet()) {
System.out.println(header.getKey() + "=" + header.getValue());
}
conn.disconnect();
}
public static void main(String[] args) throws MalformedURLException, IOException {
// HERE : Enable JDK logging for class
// sun.net.www.protocol.http.HttpURLConnection
getSomething("http://www.goodle.com");
// HERE: Disable JDK logging for class
// sun.net.www.protocol.http.HttpURLConnection
getSomething("http://www.microsoft.com");
}
}
In other words: before the first URL call the logging must be enabled and then disabled before the next call.
That is the challenge !
I'm unable to figure out how to do it.
Must work with Java 7.
Note:
I can do it by using configuration file, logging.properties :
sun.net.www.protocol.http.HttpURLConnection.level = ALL
but I want to have a programmatic solution.
UPDATE
Here's code that works in Java 6 but not in Java 7:
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map.Entry;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
public class HttpLoggingTest {
/**
Just a dummy to get some action from HttpURLConnection
*/
private static void getSomething(String urlStr) throws MalformedURLException, IOException {
System.out.println("----- " + urlStr);
HttpURLConnection conn = (HttpURLConnection) new URL("http://www.google.com").openConnection();
for (Entry<String, List<String>> header : conn.getHeaderFields().entrySet()) {
System.out.println(header.getKey() + "=" + header.getValue());
}
conn.disconnect();
}
private static void enableConsoleHandler() {
//get the top Logger
Logger topLogger = java.util.logging.Logger.getLogger("");
// Handler for console (reuse it if it already exists)
Handler consoleHandler = null;
//see if there is already a console handler
for (Handler handler : topLogger.getHandlers()) {
if (handler instanceof ConsoleHandler) {
//found the console handler
consoleHandler = handler;
break;
}
}
if (consoleHandler == null) {
//there was no console handler found, create a new one
consoleHandler = new ConsoleHandler();
topLogger.addHandler(consoleHandler);
}
consoleHandler.setLevel(Level.ALL);
}
public static void main(String[] args) throws MalformedURLException, IOException {
enableConsoleHandler();
final Logger httpLogger = Logger.getLogger("sun.net.www.protocol.http.HttpURLConnection");
// Enable JDK logging for class
//sun.net.www.protocol.http.HttpURLConnection
httpLogger.setLevel(java.util.logging.Level.FINE);
getSomething("http://www.goodle.com");
// Disable JDK logging for class
// sun.net.www.protocol.http.HttpURLConnection
httpLogger.setLevel(java.util.logging.Level.INFO);
getSomething("http://www.microsoft.com");
}
}
UPDATE2
In order to make sure that a solution only enables output from our target class (and not all sorts of other JDK internal classes) I've created this minimal JAXB example. Here JAXB is simply an example of 'something else', it could have been any other part of the JDK that also use PlatformLogger.
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Minimal dummy JAXB example. Only purpose is to provoke
* some JAXB action. Non-prod quality!
*/
#XmlRootElement(name = "book")
public class Celebrity {
#XmlElement
public String getFirstName() {
return "Marilyn";
}
#XmlElement
public String getLastName() {
return "Monroe";
}
public void printXML() {
JAXBContext context;
try {
context = JAXBContext.newInstance(Celebrity.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(this, System.out);
} catch (JAXBException ex) {
}
}
}
Instantiate an instance of the Celebrity class and call printXML(). Put that into getSomething() method. This must not generate JAXB internal logging output ... or else you've enabled logging for more than you thought.
Stumbled over PlatformLoggingMXBean the other day. I'll need to try something like:
PlatformLoggingMXBean platformLoggingMXBean =
ManagementFactory.getPlatformMXBean(PlatformLoggingMXBean.class);
platformLoggingMXBean.setLoggerLevel(
"sun.net.www.protocol.http.HttpURLConnection", "FINE");
and see it it works.
Try:
java.util.logging.Logger logger =
java.util.logging.Logger.getLogger(
"sun.net.www.protocol.http.HttpURLConnection");
logger.setLevel(java.util.logging.Level.FINE);

Categories