MSF4J: Serving static content - angularjs

Can MSF4J application serve static content without using the Mustache template engine. I have developed a REST service which will be consumed by an already developed angular web app. Now I need to package the same angular app with the micro service so it will render in the browser and will consume the service via ajax calls.

MSF4J does not directly support serving static content. From your question what I understood is that you want to point the MSF4J server to a directory and serve resources in that directory by their relative path or something similar. In this case what you can do is to write an MSF4J service method with a wildcard path and serve the static content that matches the path of the request.
#Path("/")
public class FileServer {
private static final String BASE_PATH = "/your/www/dir";
#GET
#Path("/**")
public Response serveFiles(#Context Request request) {
String uri = request.getUri();
System.out.println("Requested: " + uri);
File file = Paths.get(BASE_PATH, uri).toFile();
if (file.exists()) {
return Response.ok().entity(file).build();
} else {
return Response.status(404).entity("<h1>Not Found</h1>").build();
}
}
}

Related

Load Freemarker Template from External Url

I have implemeted spring boot app where we need to send email using freemarker.
App is going to deployed on google app engine, where file structure is not available to store the templates. So, I saved templates on google storage with public access. But not able to load in freemarker template engine.
freeMarkerConfiguration.setDirectoryForTemplateLoading(new File("/home/dnilesh/Downloads/helloworld-springboot/src/main/resources/"));
content.append(FreeMarkerTemplateUtils.processTemplateIntoString(
freeMarkerConfiguration.getTemplate("Email.html"),model));
This above configuration will work on development env. But on Google app engine I dont have directory to store template.
I tried this :
freeMarkerConfiguration.setDirectoryForTemplateLoading(new File("https://storage.googleapis.com/nixon-medical/"));
content.append(FreeMarkerTemplateUtils.processTemplateIntoString(
freeMarkerConfiguration.getTemplate("Email.html"),model));
But freemarker not loading Template from External URL. How can I load this?
For external URL,you should use URLTemplateLoader:
If your template source accesses the templates through an URL, you needn't implement a TemplateLoader from scratch; you can choose to subclass freemarker.cache.URLTemplateLoader instead and just implement the URL getURL(String templateName) method.
See code sample
Though there is an accepted answer, I did not find the integration with spring boot. So I have done this
I was trying to read Freemarker template from google cloud storage with spring boot application.
So, I have done the following and it worked for me.
Implement URLTemplateLoader and only override getURL method On
FreeMarkerConfigurer bean, set pretemplate as custom template
CloudTemplateLoader - my custom loader
public class CloudTemplateLoader extends URLTemplateLoader {
private URL root;
public CloudTemplateLoader(URL root) {
super();
this.root = root;
}
#Override
protected URL getURL(String template) {
try {
return new URL(root, "/" + template);
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
}
FreeMarkerConfigurer Bean to set my custom loader
#Bean
public FreeMarkerConfigurer freeMarkerConfigurer() throws MalformedURLException {
FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
Properties properties = new Properties();
properties.setProperty("localized_lookup", "false");
freeMarkerConfigurer.setFreemarkerSettings(properties);
freeMarkerConfigurer.setPreTemplateLoaders(new CloudTemplateLoader(new URL("https://storage.googleapis.com")));
freeMarkerConfigurer.setDefaultEncoding("UTF-8");
return freeMarkerConfigurer;
}
And my controller is following
#GetMapping
public String index() {
return "<bucket-name>/index.ftl";
}
Don't forget to upload the template on the google cloud or s3. For test purpose, I added public access on my index.ftl file.
You can use a Thymeleaf resolver to load the external files.
https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html

Configuring Webpack build for file:// use in CEF

I have to develop a webapp for a CEF-Browser environment. There is no HTTP server available, everything will be served over file:// protocol.
When developing a Webapp nowadays one doesn't get round working with a framework like react/vue for frontend. The standard webpack build scripts of those build a bundle which only works served over HTTP.
Is it possible to configure webpacks build bundle to work on file:// or is there another way to use react or vue via file://?
I'm suggest read CEF wiki more carefully. You are especially interested in https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-request-handling
In short:
You can register custom scheme handler to serve resources over http+custom fake domain.
You can pack resources in zip for example if you like, or leave them at file system as is (but in that case you can expect that some funny users can edit your files, and then report back unexisting errors back to you).
Important helpers already done (but you can write own when need.)
You can... many other things.
Main thing that "file" scheme are more restricted, and for example you can't do XHR requests. But for custom handler you can. Even if dynamic loader for some reason use XHR instead DOM-based loading it will work again same as on http without touching network.
cefclient itself also has usage of custom schemes. Check URL of Tests->Other... in menu. :)
PS: Sorry that my answer doesnt have direct answer for your question. But, custom resource handling in CEF is so common, that i'm just should say about.
fddima is right - you don't need to configure your webpack (although it would be theoretically possible). Instead you can use custom scheme handler in CEF. I made it work with angular at work.
I wrote blog post on how to serve web application via 'file' protocol in CEF.
What you want to add is your scheme handler and its factory:
using System;
using System.IO;
using CefSharp;
namespace MyProject.CustomProtocol
{
public class CustomProtocolSchemeHandler : ResourceHandler
{
// Specifies where you bundled app resides.
// Basically path to your index.html
private string frontendFolderPath;
public CustomProtocolSchemeHandler()
{
frontendFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "./bundle/");
}
// Process request and craft response.
public override bool ProcessRequestAsync(IRequest request, ICallback callback)
{
var uri = new Uri(request.Url);
var fileName = uri.AbsolutePath;
var requestedFilePath = frontendFolderPath + fileName;
if (File.Exists(requestedFilePath))
{
byte[] bytes = File.ReadAllBytes(requestedFilePath);
Stream = new MemoryStream(bytes);
var fileExtension = Path.GetExtension(fileName);
MimeType = GetMimeType(fileExtension);
callback.Continue();
return true;
}
callback.Dispose();
return false;
}
}
public class CustomProtocolSchemeHandlerFactory : ISchemeHandlerFactory
{
public const string SchemeName = "customFileProtocol";
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
{
return new CustomProtocolSchemeHandler();
}
}
}
And then register it before calling Cef.Initialize:
var settings = new CefSettings
{
BrowserSubprocessPath = GetCefExecutablePath()
};
settings.RegisterScheme(new CefCustomScheme
{
SchemeName = CustomProtocolSchemeHandlerFactory.SchemeName,
SchemeHandlerFactory = new CustomProtocolSchemeHandlerFactory()
});

How can I get AngularJS working with the ServiceStack FallbackRoute attribute to support HTML5 pushstate Urls?

I am building a client/server solution, using an AngularJS Single Page App as the client component and a Self-Host ServiceStack RESTful API as the server component. A single Visual Studio Console Application Project holds HTML and JavaScript files for the AngularJS component, along with C# classes for bootstrapping the ServiceStack AppHost (I have devolved Interface and Service responsibilities to separate Visual Studio Projects).
I have set all HTML and JavaScript files to have a 'Build Action' of 'None' and a 'Copy to Output Directory' of 'Copy if newer'.
Everything is working very well as long as I am prepared to put up with having a '#' in my site URLs. I would like to eliminate this by using HTML5 pushstate URLs.
Effectively this means I need to persuade ServiceStack to serve up my default Single Page App HTML shell page whenever a non-existent route is requested. There is now a FallbackRoute attribute available in ServiceStack which appears to have been added exactly for this purpose.
However, I am unsure how to use it. I have found people asking similar questions here, here and here. But the answers given were all before the new FallbackRoute attribute arrived.
Essentially, I am looking for a simple, yet complete example of how to use the FallbackRoute attribute to ensure any requests to non-existent routes are redirected to a single static HTML page.
The RazorRockstars.Web has an implementation. I'll modify it to use a wildcard path and a default view:
[FallbackRoute("/{Path*}")]
public class Fallback
{
public string Path { get; set; }
public string PathInfo { get; set; }
}
public class RockstarsService : Service
{
[DefaultView("Index")]
public object Any(Fallback request)
{
request.PathInfo = base.Request.PathInfo;
return request;
}
// ...
}
Since this is a service it requires a View page (details here) rather than a content page.
In the RockStars example, I can't determine what view would be rendered for the FallBackResponse, but setting the view explicitly should be all you need.
The [DefaultView("Index")] attribute I added to the Any method maps the response to a Views/Index.cshtml file. The Index.cshtml file can be empty but for a template declaration, and the complete markup for your single page app can be in your template file (i.e. _Layout.cshtml)
Without Razor
Read the html into a string and return it, while setting the content type to "text/html" with an attribute, see wiki docs on service return types
public class RockstarsService : Service
{
static string readContents;
[AddHeader(ContentType = "text/html")]
public string Any(Fallback request)
{
// check timestamp for changes for production use
if (readContents == '') {
using (StreamReader streamReader = new StreamReader(pathFromConfigFile, Encoding.UTF8))
{
readContents = streamReader.ReadToEnd();
}
}
return readContents;
}
// ...
}
It turns out it is all very simple with the FallbackRoute functionality, once you work out how to use it properly:
[FallbackRoute("/{Path*}")]
public class Fallback
{
public string Path { get; set; }
}
public class FallBackService : Service
{
public object Any(Fallback request)
{
return new HttpResult(new FileInfo("index.html")) {ContentType = "text/html"};
}
}
Once this is in place, I find 'index.html' is indeed getting served up whenever I try to hit a non-existent route.
Any static files, such as JavaScript and CSS resources, get served up as normal (as long as they have a 'Copy to Output Directory' setting of 'Copy if newer', of course).
This works like a charm with the HTML5 Push-state functionality in AngularJS.

which are the files uri on GAE java emulating cloud storage with GCS client library?

I'm developing a web application using Google app engine for Java.
I will use Google Cloud storage and according to the documentation, I'm using GCS client library to emulate cloud storage on local disk.
I have no problem saving the files, I can see them from eclipse under the war folder (under the path WEB-INF/appengine-generated) and I can see them from the web admin panel accessible from the url
localhost:8888/_ah/admin
as indicated in this question
My question is the following. Which are the files URI under localhost to access them with GCS emulation?
Example of one of uploaded files on localhost:
file key is aglub19hcHBfaWRyJwsSF19haF9GYWtlQ2xvdWRTdG9yYWdlX18xIgpxcmNvZGUuanBnDA
ID/name is encoded_gs_key:L2dzLzEvcXJjb2RlLmpwZw
filename is /gs/1/qrcode.jpg
Thanks in advance.
You can see how this is done here:
https://code.google.com/p/appengine-gcs-client/source/browse/trunk/java/src/main/java/com/google/appengine/tools/cloudstorage/dev/LocalRawGcsService.java
As of today this mapping is being maintained by the using the local datastore. This may change in the future, but you should be able to simply call into this class or one of the higher level classes provided with the GCS client to get at the data.
Using getServingUrl()
The local gcs file is saved into a blob format.
When saving it, I can use location like your filename "/gs/1/qrcode.jpg"
Yet, when accessing it, this fake location is not working.
I found a way. It may not be the best, but works for me.
BlobKey bk = BlobstoreServiceFactory.getBlobstoreService().createGsBlobKey(location);
String url = ImagesServiceFactory.getImagesService().getServingUrl(bk);
The url will be like:
http://127.0.0.1:8080/_ah/img/encoded_gs_key:yourkey
(I was hardly to find any direct solution by google search.
I hope this answer can help others in need.)
Resource: ImagesServiceFactory ImageService
FileServiceFactory
For those who wish to serve the local GCS files that have been created by the GAE GCS library, one solution is to expose a Java Servlet like this:
package my.applicaion.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
public final class GoogleCloudStorageServlet
extends HttpServlet
{
#Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException
{
final BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
final String fileName = "/gs" + request.getPathInfo();
final BlobKey blobKey = blobstoreService.createGsBlobKey(fileName);
blobstoreService.serve(blobKey, response);
}
}
and in your web.xml:
<servlet>
<servlet-name>GoogleCloudStorage</servlet-name>
<servlet-class>my.applicaion.servlet.GoogleCloudStorageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GoogleCloudStorage</servlet-name>
<url-pattern>/gcs/*</url-pattern>
</servlet-mapping>
If you host this servlet in your GAE application, the URL for accessing a GCS file with bucket bucket-name and with name fileName is http://localhost:8181:/gcs/bucket-name/fileName, the local GAE development server port number being 8181.
This works at least from GAE v1.9.50.
And if you intend to have the local GCS server working in a unit test with Jetty, here is a work-around, hopefully with the right comments:
final int localGcsPortNumber = 8081;
final Server localGcsServer = new Server(localGcsPortNumber);
final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
final String allPathSpec = "/*";
context.addServlet(new ServletHolder(new HttpServlet()
{
#Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
final BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
final String fileName = "/gs" + request.getRequestURI();
final BlobKey blobKey = blobstoreService.createGsBlobKey(fileName);
if (blobKey != null)
{
// This is a work-around over the "ServeBlobFilter" which does not take the "Content-Type" from the "blobInfo", but attempts to retrieve it from the "blobKey"
final BlobInfo blobInfo = BlobStorageFactory.getBlobInfoStorage().loadGsFileInfo(blobKey);
if (blobInfo != null)
{
final String contentType = blobInfo.getContentType();
if (contentType != null)
{
response.addHeader(HttpHeaders.CONTENT_TYPE, contentType);
}
}
}
blobstoreService.serve(blobKey, response);
}
}), allPathSpec);
// The filter is responsible for taken the "blobKey" from the HTTP header and for fulfilling the response with the corresponding GCS content
context.addFilter(ServeBlobFilter.class, allPathSpec, EnumSet.of(DispatcherType.REQUEST));
// This attribute must be set, otherwise a "NullPointerException" is thrown
context.getServletContext().setAttribute("com.google.appengine.devappserver.ApiProxyLocal", LocalServiceTestHelper.getApiProxyLocal());
localGcsServer.setHandler(context);
localGcsServer.start();

Html5 pushstate Urls on ServiceStack

At the moment we're using a default.cshtml view in the root of ServiceStack to serve our AngularJS single-page app.
What I'd like to do is enable support for html5 pushstate (so no hash in the URL), but the only examples I've found so far involve a dependency on MVC with a wildcard route, and pushing the ServiceStack infrastructure to a /api subroute.
We can't take the MVC dependency, so I think we need for accept:text/html requests we need to accept any url and serve up our root application. I'd be happy to remove the default HtmlFormat extension or override it (we could still use it's JsonReport content type it we needed to).
How can I best approach this?
The Order of Operations wiki page shows the number of different hooks you can tap into to inject your own customized behavior as well as the order which they are run.
Hi-jacking requests with RawHttpHandlers
You can by-pass ServiceStack completely by adding a Config.RawHttpHandlers to return a IHttpHandler on requests you want to hi-jack, e.g this is how the built-in mini profiler hi-jacks all requests for files that start with ssr- and returns the physical file:
config.RawHttpHandlers.Add((IHttpRequest request) => {
var file = GetFileNameWithoutExtension(request.PathInfo);
return file != null && file.StartsWith("ssr-")
? new MiniProfilerHandler()
: null;
}
Providing a fallback handler for non-matching routes
If you want to provide a default handler for non-matching route you can register a CatchAllHandlers in AppHost.Configure() or in a plugin with:
appHost.CatchAllHandlers.Add((string method, string pathInfo, string filepath) =>
{
return ShouldProvideDefaultPage(pathInfo)
? new RazorHandler("/defaultpage.cshtml")
: null;
});
Using a wildcard to accept any url in a service
You could create a dummy service and simply return the same single view, e.g:
[Route("/app/{PathInfo*}")]
public class App {
public string PathInfo { get; set; }
}
public class MyService : Service
{
public object Any(App request)
{
return request;
}
}
With the wild card this service will return the view e.g. /View/App.cshtml on any route starting with /app, e.g:
/app
/app/hello
/app/hello/my/name/is?foo=bar
Partial page support
Since partial reloads is related to pushstate I'll also mention the built-in support ServiceStack has for partial reloads.
ServiceStack Docs is an example demo that uses pushstate on browsers that support it, otherwise it falls back to use full-page reloads with browsers that don't.
You can ask for a partial page with ?format=text.bare param, e.g.
Full page: http://www.servicestack.net/docs/markdown/about
Partial page: http://www.servicestack.net/docs/markdown/about?format=html.bare
Partial page markdown: http://www.servicestack.net/docs/markdown/about?format=text.bare
Although this uses Markdown Razor. In the latest ServiceStack.Razor support you can access a partial page with just: ?format=bare
Expanding on my comment. This is what I ended up with trying to host an application in /app while also supporting the virtual file system.
host.CatchAllHandlers.Add((string method, string pathInfo, string filepath) =>
{
if (!Regex.IsMatch(pathInfo, "^/app([/?]|$)"))
return null;
// Serve valid requests as is
var vFile = HostContext.ResolveVirtualFile(pathInfo, null);
if (vFile != null)
return null;
var vDir = HostContext.ResolveVirtualDirectory(pathInfo, null);
if (vDir != null && vDir.GetDefaultDocument() != null)
return null;
// Fallback to default document
var vDef = HostContext.ResolveVirtualDirectory("/app/", null).GetDefaultDocument();
return new CustomResponseHandler((req, res) =>
new HttpResult(vDef.OpenRead(), MimeTypes.GetMimeType(vDef.Name)));
});

Resources