I am trying to understand how the Session scoped bean work and have tried the example from here.
HelloMessageGenerator.java
public class HelloMessageGenerator {
private String message;
public HelloMessageGenerator() {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
HelloMessageBean.java
#Configuration
public class HelloMessageBean {
#Bean
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator requestScopedBean() {
System.out.println("bean created");
return new HelloMessageGenerator();
}
}
HelloMessageController.java
#Controller
public class HelloMessageController {
#Resource(name = "sessionScopedBean")
HelloMessageGenerator sessionScopedBean;
#RequestMapping("/scopes/session")
public String getSessionScopeMessage(final Model model) {
model.addAttribute("previousMessage", sessionScopedBean.getMessage());
sessionScopedBean.setMessage("Good afternoon!");
model.addAttribute("currentMessage", sessionScopedBean.getMessage());
return "scopesExample";
}
}
When I go to http://localhost:8080/scopes/session I get an error.
scopesExample.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<p th:text="${previousMessage}">previous message</p>
<p th:text="${currentMessage}">current message</p>
</body>
</html>
The error I am getting is as if the mapping would not exist:
This application has no explicit mapping for /error, so you are seeing this as a fallback. There was an unexpected error (type=Not Found, status=404).
I was missing the thymeleaf dependency in the pom.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
This is why I was getting a 404 when going to localhost:8080/scopes/request
After adding the thymeleaf dependency the bean scope was working as expected. For a session scope bean the bean is created only once per session or after the configured session timeout. For a request scope bean it is created for every request (ie. each time one hits the endpoint).
Related
I am not sure how to test thymeleaf controller views in a spring boot webflux application.
The following gives 404 BAD REQUEST:
webTestClient.get()
.uri("/customer-view")
.accept(MediaType.TEXT_HTML)a
.exchange().isOk
I believe that webTestClient doesn't give any server-side views testing support, only mockMvc. But I have only the webflux dependency "org.springframework.boot:spring-boot-starter-webflux", and mockMvc is not present in webflux as it's a blocking client of course.
I am wondering if there is a way to test a thymeleaf view in webflux. For example a method like the following:
#Controller
class CustomerController {
#GetMapping("/customer-view")
suspend fun getCustomerView(model: Model
): String {
val customer = customerService.getCustomer()
model["customer"] = customer
return "customer-view"
}
}
Thanks
My understanding is thymeleaf requires resources to be present, so while you are testing you need to load resources as well.
#Controller
class CustomerController {
#GetMapping("/customer-view")
suspend fun getCustomerView(model:Model):String{
model["something"] = "this is an example"
return "customer-view"
}
}
// make sure that your html template here -> /resource/templates/cusotmer-view.html . Remember to add xmlns attribute to your template, too.
<!DOCTYPE html>
<html xmlns:th="https://www.thymelaf.org" lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${something}"/>
</body>
</html>
// Now you can test this
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest
import org.springframework.test.web.reactive.server.WebTestClient
// Again you need resources so add this annotation #WebFluxTest to load them
#WebFluxTest
class CustomerControllerTest{
#Autowired
private lateinit var webTestClient: WebTestClient
#Test
fun testReturns200Ok() {
runBlocking {
webTestClient
.get()
.uri("/customer-view")
.exchange()
.expectStatus().isOk
}
}
}
// I hope this helps
Modifying umit's answer - removing blocking
#RunWith(SpringRunner::class)
#WebFluxTest(CustomerController::class)
class CustomerControllerTest {
#Autowired
private lateinit var webTestClient: WebTestClient
#Test
fun `should return customer view`() {
val customer = Customer("John", "Doe")
`when`(customerService.getCustomer()).thenReturn(customer)
webTestClient.get().uri("/customer-view")
.exchange()
.expectStatus().isOk
.expectBody<String>()
.consumeWith {
//assertions
}
}
}
I'm trying to upload .pdf file with jQuery AJAX to Spring MVC 5 with Spring Security 5 back-end running on Tomcat and faced multiple issues depending on Spring configuration
NOTE:
File upload should be available without authentication
Front-end
Markup:
<div id="upload-modal" class="modal">
<div class="modal-content">
<h4>Upload</h4>
<form action="#" enctype="multipart/form-data">
<div class="file-field input-field">
<div class="btn">
<span>View...</span>
<input type="file" name="file" accept="application/pdf">
</div>
<div class="file-path-wrapper">
<label>
<input class="file-path validate" type="text">
</label>
</div>
</div>
</form>
</div>
<div class="modal-footer">
Cancel
Upload
</div>
</div>
csrf header for all the requests:
$(document).ready(function () {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function (e, xhr, options) {
xhr.setRequestHeader(header, token);
});
});
Uploading with jQuery AJAX:
$("#upload-bttn").click(function () {
var $uploadModal = $("#upload-modal");
const fileName = $uploadModal.find(".file-path").val();
const extension = fileName.substr(fileName.lastIndexOf(".") + 1);
if (extension === "pdf") {
$.ajax({
url: "/upload",
type: "POST",
data: new FormData($uploadModal.find("form").get(0)),
processData: false,
contentType: false,
success: function () {
console.log("success")
},
error: function () {
console.log("error")
}
});
} else {
M.toast({html: 'Selected file is not .pdf'});
}
});
Back-end
General configuration looks like below. It is modified depending on the cases
Security Initialization:
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityInitializer() {
super(SecurityContext.class);
}
#Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
}
Application initialization:
public class ApplicationInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.COOKIE));
servletContext.getSessionCookieConfig().setHttpOnly(true);
servletContext.getSessionCookieConfig().setSecure(true);
AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
dispatcherServlet.register(WebAppContext.class);
ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
}
Case 1 - CommonsMultipartResolver bean definition
CommonsMultipartResolver bean definition:
#Bean
public CommonsMultipartResolver multipartResolver(
#Value("${max.upload.size}") Integer maxNumber,
#Value("${max.size}") Integer maxSize) {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(1024 * maxSize * maxNumber);
resolver.setMaxUploadSizePerFile(maxSize);
resolver.setMaxInMemorySize(maxSize);
resolver.setDefaultEncoding("UTF-8");
try {
resolver.setUploadTempDir(new FileSystemResource(System.getProperty("java.io.tmpdir")));
} catch (IOException e) {
e.printStackTrace();
}
return resolver;
}
I remember there was strange Spring behavior when MultipartResolver bean should be named "multipartResolver" explicitly. I tried both #Bean and #Bean("multipartResolver") with configuration above and had same result (despite bean above is named "multipartResolver" as per method name)
Result:
Error 500 - Unable to process parts as no multi-part configuration has been provided
Case 2 - MultipartConfigElement in Servlet registry
removed CommonsMultipartResolver bean
added StandardServletMultipartResolver bean
added MultipartConfigElement to ApplicationInitializer
StandardServletMultipartResolver bean definition:
#Bean
public StandardServletMultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
Updated ApplicationInitializer:
#Override
public void onStartup(ServletContext servletContext) {
...
servlet.setMultipartConfig(new MultipartConfigElement(
System.getProperty("java.io.tmpdir")
));
}
As per Spring documentation:
Ensure that the MultipartFilter is specified before the Spring Security filter. Specifying the MultipartFilter after the Spring Security filter means that there is no authorization for invoking the MultipartFilter which means anyone can place temporary files on your server. However, only authorized users will be able to submit a File that is processed by your application
As I need to allow not authenticated users to upload the files I tried both before and after in SecurityInitializer as below with the same result
#Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
or
#Override
protected void afterSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
Result:
Error 403
Questions
What do I miss in the configuration?
Thoughts
CommonsMultipartResolver would be preferable as allows to drive it with Spring properties
Something wrong with Spring Security context setup
There is allowCasualMultipartParsing="true" option (did not test) which I wouldn't like to stick to as its Tomcat specific
Updates
With disabled Spring Security everything works properly
http.authorizeRequests().antMatchers("/**").permitAll(); remains as the only security context configuration so don't think its Security context configuration issue
Set multipart resolver bean name explicitly in MultipartFilter in
beforeSpringSecurityFilterChain(ServletContext servletContext) and still no luck
Adding of _csrf token to the request header did not work for both cases
Realized that I miss additional WebAppContext class in SecurityInitializer constructor. Now error 500 disappeared but 403 appeared for case 1. Logging says that I have invalid csrf token despite I added it to the header like above
Tried to submit the form with csrf token including hidden input <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> yet the result is the same - error 403 with invalid token statement
After two days of struggling:
Constructor should contain both security and application context configuration classes
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityInitializer() {
super(SecurityContext.class, WebAppContext.class);
}
}
Application context (WebAppContext) should contain MultipartResolver bean definition
#Bean
public CommonsMultipartResolver multipartResolver(
#Value("${max.upload.size}") Integer maxNumber,
#Value("${max.size}") Integer maxSize) {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(1024 * maxSize * maxNumber);
resolver.setMaxUploadSizePerFile(maxSize);
resolver.setMaxInMemorySize(maxSize);
resolver.setDefaultEncoding("UTF-8");
try {
resolver.setUploadTempDir(new FileSystemResource(System.getProperty("java.io.tmpdir")));
} catch (IOException e) {
e.printStackTrace();
}
return resolver;
}
In my case after application initialization csrf token inside Spring CsrfTokenRepository was empty for some reason so when Spring been comparing token from client request header with null in CsrfFilter Spring was returning error 403. I configured csrf in security context in the following way:
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http.csrf().csrfTokenRepository(new CookieCsrfTokenRepository());
...
}
Now csrf token is passed in cookies with first server response to the browser and the repository generates and caches a token to compare against the one coming from the client so comparison passes successfully
Here CookieCsrfTokenRepository may also be declared as CookieCsrfTokenRepository.withHttpOnlyFalse() if you would like to grab the token from cookie and set it into csrf header, but I have chosen to go with meta tags approach above
In my current project I have a number of Thymeleaf fragments stored under the src tree src/main/resources/templates/fragments. In my test suite i have a number of test templates that will pull in individual fragments so that I may process them and compare the output with some test output files. These are located in src/test/resources/test-templates/input and look like the following:
title-and-subtitle-test-input.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">>
<body>
<table
th:replace="~{fragments/titleAndSubtitle :: titleAndSubtitleTable(titleText = 'Test Title', subtitleText = 'Test Subtitle')}"></table>
</body>
</html>
and the titleAndSubtitle fragment looks like this:
titleAndSubtitle.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<body>
<table class="fragment" th:fragment="titleAndSubtitleTable(titleText, subtitleText)">
<tr>
<td class="title">[[${titleText}]]</td>
</tr>
<tr>
<td class="subtitle">[[${subtitleText}]]</td>
</tr>
</table>
</body>
</html>
and my test thymeleaf config class looks as follows:
ThymeleafTestConfiguration.java
#Configuration
public class ThymeleafTestConfiguration extends WebMvcConfigurerAdapter implements ApplicationContextAware {
private static final String TEST_TEMPLATE_DIRECTORY = "classpath:/test-templates/input/";
private static final String TEMPLATE_SUFFIX = ".html";
#Autowired
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* THYMELEAF: View Resolver - implementation of Spring's ViewResolver
* interface.
*
* #return ViewResolver
*/
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
return viewResolver;
}
/**
* THYMELEAF: Template Engine (Spring4-specific version).
*
* #return SpringTemplateEngine
*/
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setEnableSpringELCompiler(true);
templateEngine.addTemplateResolver(templateResolver());
return templateEngine;
}
/**
* THYMELEAF: Template Resolver for test templates.
*
* #return TemplateResolver
*/
private ITemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix(TEST_TEMPLATE_DIRECTORY);
templateResolver.setSuffix(TEMPLATE_SUFFIX);
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setOrder(1);
return templateResolver;
}
}
I haven't been able to correctly configure a Thymeleaf template resolver and engine to be able to locate the test input fiel and still resolve the fragment expression, i.e. pull in the fragment from the src tree. Instead I decided to copy the contents of my fragment directory in the src tree to a temporary folder - src/test/resources/test-templates/input/fragments, with the idea being that the fragments are placed in the test resource temporarily so the thymeleaf template resolver could successfully locate and retrieve them, pull them into the test template for processing, and once all tests have ran the temp directory would be deleted. I am able to successfully copy the directory and its contents to the test tree but I'm getting errors like :
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [test-templates/input//fragments/titleAndSubtitle.html]" - line 5, col 9)
//etc
Caused by: java.io.FileNotFoundException: class path resource [test-templates/input/fragments/titleAndSubtitle.html] cannot be opened because it does not exist
This is similar to the errors I was seeing when I was still messing around with template resolvers. The test looks as follows:
FragmentTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = ThymeleafTestConfiguration.class)
public class ClaimSnapshotFragmentTest {
private static File sourceFragmentDirectory = new File("src/main/resources/templates/fragments");
private static File tempFragmentDirectory = new File("src/test/resources/test-templates/input/fragments");
#BeforeClass
public static void setupClass() throws Exception {
if(tempFragmentDirectory.exists()) {
deleteDirectory(tempFragmentDirectory);
}
copyDirectory(sourceFragmentDirectory, tempFragmentDirectory);
}
#AfterClass
public static void teardownClass() throws Exception {
if(tempFragmentDirectory.exists()) {
deleteDirectory(tempFragmentDirectory);
}
}
#Autowired
private SpringTemplateEngine templateEngine;
#Test
public void test_titleAndSubtitle_fragmentParsedWithData() throws Exception {
final String expected = FileUtils.getFileFromFolderAsString("title-and-subtitle-test-expected-output.html",
FOLDER_TEST_TEMPLATES_OUTPUT);
final String actual = templateEngine.process("title-and-subtitle-test-input", new Context());
final Diff diff = DiffBuilder
.compare(expected)
.withTest(actual)
.checkForSimilar()
.normalizeWhitespace()
.build();
assertThat(diff.hasDifferences()).isFalse();
}
Am I missing something with the Thymeleaf configuration? I have checked via code and manually that the fragments directory is created, contains the fragment template in question, and is successfully removed after the tests have ran but I'm still getting an error about files not existing. Note: I am using Thymeleaf version 3.0.9.RELEASE with Thymeleaf layout dialect version 2.3.0. The application runs Thymeleaf in "offline mode" and is used primarily to create email bodies by combining data received by MQ and processing it in a template. The copyDirectory and deleteDirectory methods are from Apache Commons FileUtils class
MyController.java:
#Controller
public class ForemanController {
#RequestMapping({"/index", "/"})
public ModelAndView home(Model model){
Map<String, String> map = new HashMap<String, String>();
// .. fill map
return new ModelAndView("index", "map", map);
}
}
ServletInitializer.java:
public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[0];
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{AppConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
AppConfig.java:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.my"})
public class AppConfig {
#Bean
public VelocityConfigurer velocityConfig(){
VelocityConfigurer velocityConfig = new VelocityConfigurer();
velocityConfig.setResourceLoaderPath("/");
return velocityConfig;
}
#Bean
public VelocityLayoutViewResolver viewResolver(){
VelocityLayoutViewResolver viewResolver = new VelocityLayoutViewResolver();
viewResolver.setCache(true);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".vm");
return viewResolver;
}
}
index.vm under WEB-INF/views:
<!DOCTYPE HTML>
<html>
<head>
<title>foreman</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
hello world!
</body>
</html>
I deploy to Wildfly, deployment successful, hit the home page with 'localhost:8080/myapp' and I get Internal Server Error:
2016-03-11 01:48:58,844 ERROR [org.apache.velocity] (default task-11) ResourceManager : unable to find resource 'layout.vm' in any resource loader.
I see no mention of 'layout' anywhere in my project. Where is this coming from?
It is default behavior of VelocityLayoutViewResolver in your bean viewResolver to search for a template layout.vm.
layout.vm is expected to serve as a frame or wrapper around the views determined by your controller. That's very handy, because you don't need to bother about how a special view and your general HTML page are merged.
Please see this for example this tutorial (start at 'Creating templates') and this question for details.
I want to develop my first AppEngine application, that will also use GWT. Since I don't have any experience with GWT and AppEngine, I started with tutorials on GWT site, and after succefully completing Getting Started, I started working on http://code.google.com/webtoolkit/doc/latest/tutorial/appengine.html
But I ran into a problem, and I don't have a clue why :)
I am trying to check if user is logged in, like in "Personalize the application with the User Service" section of tutorial.
But when I run the code itself, I get an error:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>Error 404 NOT_FOUND</title>
</head>
<body><h2>HTTP ERROR 404</h2>
<p>Problem accessing /parkmeweb/login. Reason:
<pre> NOT_FOUND</pre></p><hr /><i><small>Powered by Jetty://</small></i><br/>
</body>
</html>
And here are my files:
LoginService
#RemoteServiceRelativePath("login")
public interface LoginService extends RemoteService {
public LoginInfo login(String requestUri);
}
LoginServiceAsync
public interface LoginServiceAsync {
public void login(String requestUri, AsyncCallback<LoginInfo> async);
}
LoginServiceImpl
public class LoginServiceImpl extends RemoteServiceServlet implements
LoginService {
public LoginInfo login(String requestUri) {
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
LoginInfo loginInfo = new LoginInfo();
if (user != null) {
loginInfo.setLoggedIn(true);
loginInfo.setEmailAddress(user.getEmail());
loginInfo.setNickname(user.getNickname());
loginInfo.setLogoutUrl(userService.createLogoutURL(requestUri));
} else {
loginInfo.setLoggedIn(false);
loginInfo.setLoginUrl(userService.createLoginURL(requestUri));
}
return loginInfo;
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!-- Servlets -->
<servlet>
<servlet-name>loginService</servlet-name>
<servlet-class>com.parkme.parkmeweb.server.LoginServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>loginService</servlet-name>
<url-pattern>/parkmeweb/login/</url-pattern>
</servlet-mapping>
<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>ParkmeWeb.html</welcome-file>
</welcome-file-list>
</web-app>
All this I getting called from onModuleLoad:
public void onModuleLoad() {
LoginServiceAsync loginService = GWT.create(LoginService.class);
loginService.login(GWT.getHostPageBaseURL(), new AsyncCallback<LoginInfo>() {
public void onFailure(Throwable error) {
//this is where error is thrown
Window.alert(error.getMessage());
}
public void onSuccess(LoginInfo result) {
loginInfo = result;
if(loginInfo.isLoggedIn()) {
return;
} else {
loadLogin();
}
}
});
}
Just by looking at this, I can't see any problems, and I should probably looking for problems elsewhere, but I would like to hear some ideas what went wrong.
The handler is for /parkmweweb/login/, but you're visiting /parkmeweb/login - without the trailing slash.
Facing the same problem. But I tried to deploy it to google. The servlet is accessible and no problem. It looks like being a problem with GWT + Eclipse, not sure exactly where. Hope they can fix it, other wise testing is difficult.
I just restarted Eclipse and that fixed the problem.
Problem started when I switched from jre1.7 to jre1.6 both x64.