Showing posts with label spring boot. Show all posts
Showing posts with label spring boot. Show all posts

Tuesday, March 24, 2015

Spring boot and CORS (Cross Origin Resource Sharing)

Another example of use - spring boot

Sometimes a web application needs access to local resources (filesystem, printers, devices, etc).
The access is possible through Java Applets, ActiveX, browser plugins or ... a client application exposing REST API and the web application called by the client application through the Cross Domain AJAX call. This post describes how  you can create a local microserver called by browser in (about) 5 lines of code with spring boot.

The creation of a microserver with spring boot is very easy. Simply annotate your class with @RestController and your methods with @RequestMapper.  That's it.

But when the browser calls the local mircoserver as it follows:

$.ajax({
url: 'http://localhost:8080/rest/printers',
type: 'GET',
dataType: 'json',
async: true,
cache: false,
crossDomain: true,
beforeSend: function (xhr, opts) {
var auth = make_base_auth(user, password);
console.log('auth:' + auth);
xhr.setRequestHeader('Authorization', auth);
},
success: function (resp) {
$('#result').text(resp);
},
error: function(xhr) {
alert('Error!  Status = ' + xhr.status + " Message = " + xhr.statusText);
}
});

The browser raises this exception:

XMLHttpRequest cannot load http://localhost:8080/rest/printers?_=1424781952941. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8081' is therefore not allowed access.

In fact,  when you call the external domain the browser checks your call and it runs the CORS protocol http://www.w3.org/wiki/CORS for more details.

how can you work it out?

You can use a cors-filter  adds this dependence and registers the filter

@Bean
public FilterRegistrationBean corsFilter() {
FilterRegistrationBean registrationBean =
new FilterRegistrationBean();
CORSFilter corsFilter = new CORSFilter();
Map<String, String> params = new HashMap<>();
params.put("cors.allowed.methods",CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS + ",DELETE,PUT");
params.put("cors.allowed.headers",CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS + ",authorization");
params.put("cors.logging.enabled", "true");
registrationBean.setInitParameters(params);
registrationBean.setFilter(corsFilter);
registrationBean.setUrlPatterns(Arrays.asList("/rest/*"));
return registrationBean;
}

at this point when the browser calls the external domain, the local microserver allows this call and it works.


Consideration for IE8-9

In this type of browser CORS is supported by XDomainRequest, not by XMLHttpRequest. 
This means that you need a jquery plugin jQuery-ajaxTransport-XDomainRequest to make a Cross Domain AJAX call.

IE 8-9 has more limitations. See xdomainrequest-restrictions-limitations-and-workarounds for more details and remember you must enable cross domain in security options!

To by-pass IE8-9 limitations you can add a workaround filter to microserver

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public FilterRegistrationBean createIE8_9XDomainRequestFilter() {
    FilterRegistrationBean registrationBean =
            new FilterRegistrationBean();
    registrationBean.setFilter(new IE8_9XDomainRequestFilter());
    registrationBean.setUrlPatterns(Arrays.asList("/rest/*"));
    return registrationBean;
}


source code

Monday, February 23, 2015

Spring boot 1.4 run as windows service Procrun


The challenge was starting spring boot application as windows service. I spent a long time searching on the Internet the solution but I didn't find it.

My solution was the following.

First I created an application using start.spring.io



Then, I created a project to which I added spring-boot-loader dependency

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-loader</artifactId>
            <scope>provided</scope>
        </dependency>

because of the need to extend the JarLauncher class.
Spring boot provides a special launcher that changes the java behaviour class loader. The class org.springframework.boot.loader.JarLauncher creates a special class loader and boostraps the application.

Since I wanted to launch the application as a window service, I chose Procrun as service manager. 
Procrun needs two start and stop methods or one method with string array parameter (see procrun project for more details). Therefore I created a Bootsrap class that extended JarLauncher and implemented the methods that Procrun needs.

public class Bootstrap extends JarLauncher {

    private static ClassLoader classLoader = null;
    private static Bootstrap bootstrap = null;

    protected void launch(String[] args, String mainClass, ClassLoader classLoader, boolean wait)
            throws Exception {
        // spring boot 1.2 Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
        // spring boot 1.2 Thread runnerThread = new Thread(runner);
        Thread runnerThread = new Thread(() -> {
            try {
                createMainMethodRunner(mainClass, args, classLoader).run();
            }
            catch(Exception ex) {}
        });        
        runnerThread.setContextClassLoader(classLoader);
        runnerThread.setName(Thread.currentThread().getName());
        runnerThread.start();
        if (wait == true) {
            runnerThread.join();
        }
    }

    public static void start (String []args) {
        bootstrap = new Bootstrap ();
        try {
            JarFile.registerUrlProtocolHandler();
            classLoader = bootstrap.createClassLoader(bootstrap.getClassPathArchives());
            bootstrap.launch(args, bootstrap.getMainClass(), classLoader, true);
        }
        catch (Exception ex) {
            ex.printStackTrace();
            System.exit(1);
        }
    }

    public static void stop (String []args) {
        try {
            if (bootstrap != null) {
                bootstrap.launch(args, bootstrap.getMainClass(), classLoader, true);
                bootstrap = null;
                classLoader = null;
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
            System.exit(1);
        }
    }

    public static void main(String[] args) {
        String mode = args != null && args.length > 0 ? args[0] : null;
        if ("start".equals(mode)) {
            Bootstrap.start(args);
        }
        else if ("stop".equals(mode)) {
            Bootstrap.stop(args);
        }
    }
}

In the spring boot application class I changed the main method with:

    private static ApplicationContext applicationContext = null;

    public static void main(String[] args) {
        String mode = args != null && args.length > 0 ? args[0] : null;

        if (logger.isDebugEnabled()) {
            logger.debug("PID:" + ManagementFactory.getRuntimeMXBean().getName() + 
                         " Application mode:" + mode + " context:" + applicationContext);
        }
        if (applicationContext != null && mode != null && "stop".equals(mode)) {
            System.exit(SpringApplication.exit(applicationContext, new ExitCodeGenerator() {
                @Override
                public int getExitCode() {
                    return 0;
                }
            }));
        }
        else {
            SpringApplication app = new SpringApplication(TestProcrunApplication.class);
            applicationContext = app.run(args);
            if (logger.isDebugEnabled()) {
                logger.debug("PID:" + ManagementFactory.getRuntimeMXBean().getName() + 
                             " Application started context:" + applicationContext);
            }
        }
    }

Then, I installed the service with prunsrv.exe

prunsrv.exe //IS//test-procrun --DisplayName="test-procrun" --Description="test-procrun" --Startup=auto --Install=%CD%\prunsrv.exe --Jvm=auto --Classpath=%CD%..\target\test-procrun-0.0.1-SNAPSHOT.jar --StartMode=jvm --StartClass=it.test.procrun.Bootstrap --StartMethod=start --StartParams=start --StopMode=jvm --StopClass=it.test.procrun.Bootstrap --StopMethod=stop --StopParams=stop --StdOutput=auto --StdError=auto --LogPath=%CD% --LogLevel=Debug





source code spring boot 1.2,1.3
source code spring boot 1.4