I'm trying to put together a really simple HTTP POST example using Spring Integration and a http outbound-gateway.
I need to be able to send a HTTP POST message with some POST parameters, as I would with curl:
$ curl -d 'fName=Fred&sName=Bloggs' http://localhost
I can get it working (without the POST parameters) if I send a simple String as the argument to the interface method, but I need to send a pojo, where each property of the pojo becomes a POST parameter.
I have the following SI config:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd">
<int:gateway id="requestGateway"
service-interface="RequestGateway"
default-request-channel="requestChannel"/>
<int:channel id="requestChannel"/>
<int-http:outbound-gateway request-channel="requestChannel"
url="http://localhost"
http-method="POST"
expected-response-type="java.lang.String"/>
</beans>
My RequestGateway interface looks like this:
public interface RequestGateway {
String echo(Pojo request);
}
My Pojo class looks like this:
public class Pojo {
private String fName;
private String sName;
public Pojo(String fName, String sName) {
this.fName = fName;
this.sName = sName;
}
.... getters and setters
}
And my class to kick it all off looks like this:
public class HttpClientDemo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/si-email-context.xml");
RequestGateway requestGateway = context.getBean("requestGateway", RequestGateway.class);
Pojo pojo = new Pojo("Fred", "Bloggs");
String reply = requestGateway.echo(pojo);
System.out.println("Replied with: " + reply);
}
}
When I run the above, I get:
org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [Pojo] and content type [application/x-java-serialized-object]
I've googled a lot for this, but cannot find any examples of sending HTTP POST parameters with an outbound-gateway (I can find lots about setting HTTP Headers, but that's not what I'm trying to do here)
The only thing I did find was spring-integration: how to pass post request parameters to http-outbound but it's a slightly different use case as the OP was trying to send a JSON representation of his pojo which I am not, and the answer talks about setting headers, not POST parameters.
Any help with this would be very much appreciated;
Thanks
Nathan
Thanks to the pointers from #jra077 regarding Content-Type, this is how I solved it.
My SI config now looks like this - the important bit was adding the Content-Type header:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd">
<int:gateway id="requestGateway"
service-interface="RequestGateway"
default-request-channel="requestChannel">
<int:method name="sendConfirmationEmail">
<int:header name="Content-Type" value="application/x-www-form-urlencoded"/>
</int:method>
</int:gateway>
<int:channel id="requestChannel"/>
<int-http:outbound-gateway request-channel="requestChannel"
url="http://localhost"
http-method="POST"
expected-response-type="java.lang.String"/>
</beans>
Then I changed my interface to take a Map as it's argument rather than the pojo:
public interface RequestGateway {
String echo(Map<String, String> request);
}
The pojo itself remains as before; and the class that invokes the service is changed so that it creates a Map and passes it:
public class HttpClientDemo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/si-email-context.xml");
RequestGateway requestGateway = context.getBean("requestGateway", RequestGateway.class);
Pojo pojo = new Pojo("Fred", "Bloggs");
Map<String, String> requestMap = new HashMap<String, String>();
requestMap.put("fName", pojo.getFName());
requestMap.put("sName", pojo.getSName());
String reply = requestGateway.echo(requestMap);
System.out.println("Replied with: " + reply);
}
}
I'm sure there are several more elegant ways of transforming the pojo into a Map, but for the time being this answers my question.
To send POST parameters with a http outbound-gateway you need to set the Content-Type to application/x-www-form-urlencoded and you need to pass a Map of key/values pairs.
Related
I have taken 2 PathVariable, and instead of taking these separately i want to store these 2 PathVariables in to a Map and want to retrieve it from the Map.
In Spring MVC 3.1.0 here is the my Controller class method:
#Controller
#RequestMapping("/welcome")
public class HelloController {
#RequestMapping(value="/{countryName}/{userName}",method=RequestMethod.GET)
public String getPathVar(#PathVariable Map<String, String> pathVars, Model model) {
String name = pathVars.get("userName");
String country = pathVars.get("countryName");
model.addAttribute("msg", "Welcome " + name+ " to Spring MVC & You are from" + country);
return "home";
}
My Request URL is :
http://localhost:3030/spring_mvc_demo/welcome/India/ashrumochan123
But when giving request using this url, i am getting HTTP Status 400 -
Description: The request sent by the client was syntactically incorrect.
When i am taking these Path Variables separately then it is working fine.
Here is the code -
#RequestMapping(value="/{countryName}/{userName}", method=RequestMethod.GET)
public String goHome(#PathVariable("countryName") String countryName,
#PathVariable("userName") String userName, Model model) {
model.addAttribute("msg", "Welcome " + userName
+ " to Spring MVC& You are from " + countryName);
return "home";
}
Please tell me whether i am doing anything wrong?
Any help would be greatly appreciated.
According to the Spring documentation, it's been there since version 3.2.
For the #PathVariable with Map<String,String>, I think you are missing <mvc:annotation-driven/> on your Spring servlet configuration:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<mvc:annotation-driven/>
...
I've found it on this link.
I'm trying to use org.springframework.cloud.aws.messaging.core.NotificationMessagingTemplate (from Spring Cloud AWS) to post notifications to a SNS Topic.
Every time a notification is posted a warning message is generated:
WARN [org.springframework.cloud.aws.messaging.core.TopicMessageChannel] Message header with name 'id' and type 'java.util.UUID' cannot be sent as message attribute because it is not supported by SNS.
The issue seems to be that org.springframework.messaging.MessageHeaders generates automagically an id header, of type java.util.UUID, which is not something that spring cloud knows how to handle.
Is there a way to avoid that automatic header generation (I can live without an UUID here) or avoid the warning, besides just suppressing the log?
Something similar is also affecting SQS:
Related Question:
spring-cloud-aws Spring creates message header attribute not supported by SQS
Related Bug: Warning "'java.util.UUID' cannot be sent as message attribute ..." on any request sent to SQS channel
My Controller looks something like this:
package com.stackoverflow.sample.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.aws.messaging.core.NotificationMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#Controller
#RequestMapping("/whatever")
public class SampleController {
#Autowired
private NotificationMessagingTemplate template;
#RequestMapping(method = RequestMethod.GET)
public String handleGet() {
this.template.sendNotification("message", "subject");
return "yay";
}
}
}
My Spring configuration looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aws-context="http://www.springframework.org/schema/cloud/aws/context"
xmlns:aws-messaging="http://www.springframework.org/schema/cloud/aws/messaging"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/cloud/aws/context http://www.springframework.org/schema/cloud/spring-cloud-aws-context.xsd
http://www.springframework.org/schema/cloud/aws/messaging http://www.springframework.org/schema/cloud/spring-cloud-aws-messaging.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<context:annotation-config />
<context:component-scan base-package="com.stackoverflow.sample" />
<mvc:annotation-driven />
<aws-context:context-credentials>
<aws-context:instance-profile-credentials/>
<aws-context:simple-credentials access-key="MyKey" secret-key="mySecret" />
</aws-context:context-credentials>
<aws-messaging:notification-messaging-template id="notificationMessagingTemplate" region="us-west-2" default-destination="myTopic" />
</beans>
The problem occurs because the constructor called MessageHeaders class
MessageHeaders class
MessageHeaders(Map<String, Object> headers) { } on line 39
And to not send the id header you need to call the constructor
MessageHeaders class
MessageHeaders(Map<String, Object> headers, UUID id, Long timestamp){} on line 43
because this constructor has the condition does not create the id header automatically
to stop sending the header id you need to override the MessageHeader and NotificationMessagingTemplate classes
class MessageHeaders
public class MessageHeadersCustom extends MessageHeaders {
public MessageHeadersCustom() {
super(new HashMap<String, Object>(), ID_VALUE_NONE, null);
}
}
class NotificationMessagingTemplate
public class NotificationMessagingTemplateCustom extends NotificationMessagingTemplate {
public NotificationMessagingTemplateCustom(AmazonSNS amazonSns) {
super(amazonSns);
}
#Override
public void sendNotification(Object message, String subject) {
MessageHeaders headersCustom = new MessageHeadersCustom();
headersCustom.put(TopicMessageChannel.NOTIFICATION_SUBJECT_HEADER, subject);
this.convertAndSend(getRequiredDefaultDestination(), message, headersCustom);
}
}
And finally, your class that will make the call need to use your implementation
package com.stackoverflow.sample.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.aws.messaging.core.NotificationMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#Controller
#RequestMapping("/whatever")
public class SampleController {
#Autowired
private NotificationMessagingTemplateCustom template;
#RequestMapping(method = RequestMethod.GET)
public String handleGet() {
this.template.sendNotification("message", "subject");
return "yay";
}
}
}
I am having trouble getting WCF binding to work with Blackboard Java Web Services API.
(Simple answer would be if anyone has got this working could you
please post a working binding for WCF to Blackboard)
I have spent hours trying different configurations and custom coded bindings.
Some unsuccessful attempts:
calling-a-ws-security-java-web-service-with-c-sharp-client
wcf-client-with-ws-security 12-common-wcf-interop-confusions
configure-wcf-for-ws-security-with-username-over-https
wcf-client-connecting-to-java-soap-web-service-using-ws-security
ClearUsernameBinding
There are many more to do with JAVA and WS-Security with WCF but I wont go on.
It seems that every time I get one thing working another breaks. Now I feel like I am going around in circles and just making myself even more confused.
As my first test what I am trying to do is simple Initialize the Context object and Login using an Admin test user account with a WCF proxy.
Blackboard Doc ContextWS
To make sure that all of this worked I first I downloaded the sample code for .Net WSE 2.0 and tested that, it worked perfectly.
Now when I use WCF and binding I cannot get this same behaviour.
First the Successful exchange with very old WSE 2.0
===================================
WSE 2.0 ContextWS Initialization
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<soap:Header>
<wsa:Action>initialize</wsa:Action>
<wsa:MessageID>uuid:b975e989-a4ce-4e1e-abd6-500945346c40</wsa:MessageID>
<wsa:ReplyTo>
<wsa:Address>http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsa:To>https://Blackboard.Server.Name/webapps/ws/services/Context.WS</wsa:To>
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp wsu:Id="Timestamp-47d0d017-4fd1-46c2-b1b4-2431402cf847">
<wsu:Created>2015-07-16T04:58:02Z</wsu:Created>
<wsu:Expires>2015-07-16T05:03:02Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-1b71e23a-2d84-40a5-9509-b75902ec8b76">
<wsse:Username>session</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">nosession</wsse:Password>
<wsse:Nonce>lAW2qXrXZ1maNNkCEzlHGA==</wsse:Nonce>
<wsu:Created>2015-07-16T04:58:02Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body />
</soap:Envelope>
WSE 2.0 ContextWS Initialization Success Response
<?xml version='1.0' encoding='utf-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<ns:initializeResponse xmlns:ns="http://context.ws.blackboard">
<ns:return>c2762f357bbc42a4a88d33e4e42486b8</ns:return>
</ns:initializeResponse>
</soapenv:Body>
</soapenv:Envelope>
WSE 2.0 ContextWS Login Request
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<soap:Header>
<wsa:Action>login</wsa:Action>
<wsa:MessageID>uuid:a823128b-efb4-49e1-87d9-fd35167f0bfc</wsa:MessageID>
<wsa:ReplyTo>
<wsa:Address>http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsa:To>https://Blackboard.Server.Name/webapps/ws/services/Context.WS</wsa:To>
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp wsu:Id="Timestamp-c38daf19-6b39-4391-a3f8-bcc030064a3e">
<wsu:Created>2015-07-16T04:58:15Z</wsu:Created>
<wsu:Expires>2015-07-16T05:03:15Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-65948746-e616-436a-85f4-d2e1023e39be">
<wsse:Username>session</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">c2762f357bbc42a4a88d33e4e42486b8</wsse:Password>
<wsse:Nonce>T0xs8aiaiODMK3sfKgDQtg==</wsse:Nonce>
<wsu:Created>2015-07-16T04:58:15Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<login xmlns="http://context.ws.blackboard">
<userid>test_admin</userid>
<password>TestPassword</password>
<clientVendorId>TestClient</clientVendorId>
<clientProgramId>TestPOC</clientProgramId>
<loginExtraInfo xsi:nil="true" />
<expectedLifeSeconds>10000000</expectedLifeSeconds>
</login>
</soap:Body>
</soap:Envelope>
WSE 2.0 ContextWS Login Success Response
<?xml version='1.0' encoding='utf-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<ns:loginResponse xmlns:ns="http://context.ws.blackboard">
<ns:return>true</ns:return>
</ns:loginResponse>
</soapenv:Body>
</soapenv:Envelope>
===================================
So I know that this works to our environment and I know the user can login.
Using WCF I am able to get the Initialization working but then it looses the session. It does not put the Returned Session ID into the Password field for the next message. I have tried to do this manually of course; but I get an error stating the Password field is read only.
Now for my WCF Configuration and code that has gotten me closest to the above communication.
WCF App.Config Binding
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
</system.serviceModel>
<bindings>
<customBinding>
<binding name="WCFSoapInteropJavaWS" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" >
<textMessageEncoding messageVersion="Soap11" writeEncoding="utf-8" />
<security authenticationMode="UserNameOverTransport" enableUnsecuredResponse="true" allowSerializedSigningTokenOnReply="true"
messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"
includeTimestamp="true" allowInsecureTransport="true" canRenewSecurityContextToken="false" >
</security>
<httpsTransport authenticationScheme="Anonymous" />
</binding>
</customBinding>
</bindings>
<client>
<endpoint
address="https://Blackboard.Server.Name:443/webapps/ws/services/Context.WS"
binding="customBinding" bindingConfiguration="WCFSoapInteropJavaWS"
contract="ContextWS.ContextWSPortType" name="Context.WCFSoapInteropJavaWS" />
</client>
</system.serviceModel>
</configuration>
WCF C# code
public bool testWrapper(String userId, String userPassword){
try
{
context = new ContextWrapper("Context.WCFSoapInteropJavaWS");
context.ClientCredentials.UserName.UserName = "session";
context.ClientCredentials.UserName.Password = "nosession";
context.initialize();
//context.ClientCredentials.UserName.Password = "886d935527944f94a3526288e39a555e"; // SessionGUID_HERE Throws a Read Only Error for Pasword
bool retval = context.login(userId, userPassword, vendorId, programId, null, expectedLife);
return retval;
}
catch (System.Exception e)
{
lastError = e;
return false;
}
}
This is what the SOAP communication looks like.
WCF ContextWS Initialization Request
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink">uIDPo+FmveflwUtMgSATRu3Ht9EAAAAAmYVJsX+bhUeYcTDsFqFktkqe8xmMiA1MpXouaouXgJwACQAA</VsDebuggerCausalityData>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="_0">
<u:Created>2015-07-16T07:15:05.109Z</u:Created>
<u:Expires>2015-07-16T07:20:05.109Z</u:Expires>
</u:Timestamp>
<o:UsernameToken u:Id="uuid-1237f56c-7c68-4d40-a756-7ff2c19a3235-1">
<o:Username>session</o:Username>
<o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">nosession</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
</s:Envelope>
WCF ContextWS Initialization Success Response
<?xml version='1.0' encoding='utf-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<ns:initializeResponse xmlns:ns="http://context.ws.blackboard">
<ns:return>886d935527944f94a3526288e39a555e</ns:return>
</ns:initializeResponse>
</soapenv:Body>
</soapenv:Envelope>
WCF ContextWS Login Request
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink">uIDPo+JmveflwUtMgSATRu3Ht9EAAAAAmYVJsX+bhUeYcTDsFqFktkqe8xmMiA1MpXouaouXgJwACQAA</VsDebuggerCausalityData>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="_0">
<u:Created>2015-07-16T07:15:14.033Z</u:Created>
<u:Expires>2015-07-16T07:20:14.033Z</u:Expires>
</u:Timestamp>
<o:UsernameToken u:Id="uuid-1237f56c-7c68-4d40-a756-7ff2c19a3235-1">
<o:Username>session</o:Username>
<o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">nosession</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<login xmlns="http://context.ws.blackboard">
<userid>Test_admin</userid>
<password>TestPassword</password>
<clientVendorId>TestClient</clientVendorId>
<clientProgramId>TestPOC</clientProgramId>
<loginExtraInfo xsi:nil="true"/>
<expectedLifeSeconds>10000000</expectedLifeSeconds>
</login>
</s:Body>
</s:Envelope>
WCF ContextWS Login Failed Response
<?xml version='1.0' encoding='utf-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<soapenv:Fault>
<faultcode>soapenv:Server</faultcode>
<faultstring>[WSFW001]Invalid session</faultstring>
<detail />
</soapenv:Fault>
</soapenv:Body>
</soapenv:Envelope>
As you can see the session return id has not been added to the password field on the login request so there is a "Invalid Session:
It all seemed to be going so well.
In short if anyone knows how to achieve a binding from a WCF Client to the Blackboard Java Webservice API and example would be fantastic. Other wise I am hoping that someone else that knows more about WCF Bindings to Java than I do might be able to take a look at the above see where I am going wrong.
Any help anyone can give me to get this working would be much appreciated so thanks. I really am hoping it is just something silly that I am missing.
Sorry for such a long detailed question.
Thanks to heaps of reading and some useful samples I was able to get this working. Blackboard with WCF.
Thanks go to both:
Ajadex Lopez
http://www.isyourcode.com/2010/08/attaching-oasis-username-tokens-headers.html
Johnny Lockhart
"Incoming message does not contain required Security header"
BB is buggie so you might do best to search the forum for WCF to find the post
Sample class
using System;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.ServiceModel;
using System.Xml;
using System.Security.Cryptography;
using System.Text;
namespace BBWcfWrapper
{
/// <summary>
/// Coupled with the additional classes below, allows for injecting the WS-Security header into a WCF Service call without requiring SSL on the server.
/// </summary>
/// <remarks>http://isyourcode.blogspot.com/2010/08/attaching-oasis-username-tokens-headers.html</remarks>
public class BBWSSecurityBehavior : IEndpointBehavior
{
public MessageInspector MessageInspector { get; set; }
public BBWSSecurityBehavior(MessageInspector messageInspector)
{
MessageInspector = messageInspector;
}
public void Validate(ServiceEndpoint endpoint)
{ }
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{ }
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
if (this.MessageInspector == null) throw new InvalidOperationException("Caller must supply ClientInspector.");
clientRuntime.MessageInspectors.Add(MessageInspector);
}
}
public class MessageInspector : IClientMessageInspector
{
public MessageHeader[] Headers { get; set; }
public MessageInspector(params MessageHeader[] headers)
{
Headers = headers;
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
if (Headers != null)
{
for (int i = Headers.Length - 1; i >= 0; i--)
{
request.Headers.Insert(0, Headers[i]);
}
}
return request;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
}
public class SecurityHeader : MessageHeader
{
public string SystemUser { get; set; }
public string SystemPassword { get; set; }
public SecurityHeader(string systemUser, string systemPassword)
{
SystemUser = systemUser;
SystemPassword = systemPassword;
}
public override string Name
{
get { return "Security"; }
}
public override string Namespace
{
get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
}
protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteStartElement("wsse", Name, Namespace);
writer.WriteXmlnsAttribute("wsse", Namespace);
writer.WriteAttributeString("soap", "mustUnderstand", Namespace, "1");
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
WriteHeader(writer);
}
private void WriteHeader(XmlDictionaryWriter writer)
{
var createDate = DateTime.Now;
//Start Parent Elements
writer.WriteStartElement("wsu","Timestamp","http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
writer.WriteAttributeString("wsu","id","http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd","Timestamp-6557466");
#region Start Child Elements
writer.WriteStartElement("wsu", "Created", XmlConvert.ToString(createDate, "yyyy-MM-ddTHH:mm:sszzzzzz"));
writer.WriteEndElement(); //End Created
writer.WriteStartElement("wsu", "Expires", XmlConvert.ToString(createDate.AddDays(1), "yyyy-MM-ddTHH:mm:sszzzzzz"));
writer.WriteEndElement(); //End Expires
#endregion
writer.WriteEndElement(); //End Timestamp
//Start Parent Elements
writer.WriteStartElement("wsse", "UsernameToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteXmlnsAttribute("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
#region Start Child Elements
writer.WriteStartElement("wsse", "Username", null);
writer.WriteString(SystemUser);
writer.WriteEndElement();//End Username
writer.WriteStartElement("wsse", "Password", null);
writer.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
writer.WriteString(SystemPassword);
writer.WriteEndElement();//End Password
// unique Nonce value - encode with SHA-1 for 'randomness'
// in theory the nonce could just be the GUID by itself
// This is used to stop Replay attacks
writer.WriteStartElement("wsse", "Nonce", null);
writer.WriteString(GetSHA1String(Guid.NewGuid().ToString()));
writer.WriteEndElement();//Nonce
writer.WriteStartElement("wsu", "Created", null);
writer.WriteString(XmlConvert.ToString(createDate, "yyyy-MM-ddTHH:mm:sszzzzzz"));
writer.WriteEndElement(); //End Created
#endregion
writer.WriteEndElement();//End UsernameToken
writer.Flush();
}
protected string GetSHA1String(string phrase)
{
SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
return Convert.ToBase64String(hashedDataBytes);
}
}
}
Example Use
calendar = new CalendarWrapper("Calendar.BB_WSSecurity_Binding");
//This adds a custom security Headder for WCF and Java WS-Security Interop
calendar.Endpoint.Behaviors.Add(new BBWSSecurityBehavior(new MessageInspector(BbWsAuth.SecurityHeader)));
calendar.initializeCalendarWS(false);
Simple Wrapper class
public class CalendarWrapper : CalendarWSPortTypeClient
{
public CalendarWrapper() : base() { }
public CalendarWrapper(string endpointConfigurationName) : base(endpointConfigurationName) { }
public CalendarWrapper(Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { }
}
Config
<bindings>
<basicHttpsBinding>
<binding name="BB_WSSecurity_Binding" messageEncoding="Text" textEncoding="utf-8" maxReceivedMessageSize="4000000" >
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
</binding>
</basicHttpsBinding>
</bindings>
<client>
<endpoint address="https://xxxxxxxxxx/webapps/ws/services/Calendar.WS"
binding="basicHttpsBinding" bindingConfiguration="BB_WSSecurity_Binding"
contract="CalendarWS.CalendarWSPortType" name="Calendar.BB_WSSecurity_Binding" />
I am trying to run a simple controller with a method that will receive
A String with a value
A file (MultipartFile object)
After some investigation (Sending Multipart File as POST parameters with RestTemplate requests) I ended up adding into my web.xml
<filter>
<filter-name>multipartFilter</filter-name>
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>multipartFilter</filter-name>
<url-pattern>/REST/*</url-pattern>
</filter-mapping>
My application context file
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize">
<value>104857600</value>
</property>
<property name="maxInMemorySize">
<value>4096</value>
</property>
</bean>
The controller looks like
#Controller
#RequestMapping("/image")
public class ImageController extends RestApiController {
private static final Logger log = LoggerFactory.getLogger(ImageController.class);
#RequestMapping(value="/simple", method = RequestMethod.POST, consumes="multipart/form-data")
public #ResponseBody boolean save(
#RequestParam(value = "file", required = false) MultipartFile file,
#RequestParam(value = "name", required = false) String name) {
//Some code here
return true;
}
So far I have been able to run unit tests against the controller with no problem, but I seem to be stuck when it comes to create a real http request.
I have tried with POSTMAN, but after some investigation seems that it does not set the multipart/form-data header properly, I have tried removing it, and the problem persists.
I have also tried with CURL
curl http://127.0.0.1:8080/content/REST/image/simple -F "file=#/home/jmoriano/Pictures/simple.jpeg" -F "name=someName" -v
I have also tried with a RestTemplate object
public Boolean update() {
RestTemplate restTemplate = new RestTemplate();
try {
FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
formConverter.setCharset(Charset.forName("UTF8"));
restTemplate.getMessageConverters().add(formConverter);
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setAccept(Collections.singletonList(MediaType.parseMediaType("application/json")));
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add("file", new FileSystemResource("/home/jmoriano/Pictures/simple.jpeg"));
parts.add("name", "name");
return restTemplate.postForObject("http://127.0.0.1:8080/content/REST/image/simple", parts, Boolean.class);
} catch (Exception e) {
e.printStackTrace();
log.error("Ouch!", e);
}
return false;
}
Just to be clear, the problem is not in the "name" parameter, that one works fine, however the MultipartFile is empty.
By debugging the code, I managed to check the MultiPartFilter class which receives an HttpServletRequest object, such object has its "parts" attribute already empty there. So the problem seems to be related with the way I am making the request... seems that my postman/curl/java attempts have failed... do you see anything incorrect in my config?
I think you should put the bean multipartResolver to your dispathcher-servlet.xml
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
in the link you show above they use this bean, too.
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize">
<value>104857600</value>
</property>
<property name="maxInMemorySize">
<value>4096</value>
</property>
</bean>
I have a Spring Controller with several RequestMappings for different URIs. My servlet is "ui". The servlet's base URI only works with a trailing slash. I would like my users to not have to enter the trailing slash.
This URI works:
http://localhost/myapp/ui/
This one does not:
http://localhost/myapp/ui
It gives me a HTTP Status 404 message.
The servlet and mapping from my web.xml are:
<servlet>
<servlet-name>ui</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ui</servlet-name>
<url-pattern>/ui/*</url-pattern>
</servlet-mapping>
My Controller:
#Controller
public class UiRootController {
#RequestMapping(value={"","/"})
public ModelAndView mainPage() {
DataModel model = initModel();
model.setView("intro");
return new ModelAndView("main", "model", model);
}
#RequestMapping(value={"/other"})
public ModelAndView otherPage() {
DataModel model = initModel();
model.setView("otherPage");
return new ModelAndView("other", "model", model);
}
}
Using Springboot, my app could reply both with and without trailing slash by setting #RequestMapping's "value" option to the empty string:
#RestController
#RequestMapping("/some")
public class SomeController {
// value = "/" (default) ,
// would limit valid url to that with trailing slash.
#RequestMapping(value = "", method = RequestMethod.GET)
public Collection<Student> getAllStudents() {
String msg = "getting all Students";
out.println(msg);
return StudentService.getAllStudents();
}
}
If your web application exists in the web server's webapps directory, for example webapps/myapp/ then the root of this application context can be accessed at http://localhost:8080/myapp/ assuming the default Tomcat port. This should work with or without the trailing slash, I think by default - certainly that is the case in Jetty v8.1.5
Once you hit /myapp the Spring DispatcherServlet takes over, routing requests to the <servlet-name> as configured in your web.xml, which in your case is /ui/*.
The DispatcherServlet then routes all requests from http://localhost/myapp/ui/ to the #Controllers.
In the Controller itself you can use #RequestMapping(value = "/*") for the mainPage() method, which will result in both http://localhost/myapp/ui/ and http://localhost/myapp/ui being routed to mainPage().
Note: you should also be using Spring >= v3.0.3 due to SPR-7064
For completeness, here are the files I tested this with:
src/main/java/controllers/UIRootController.java
package controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
#Controller
public class UiRootController {
#RequestMapping(value = "/*")
public ModelAndView mainPage() {
return new ModelAndView("index");
}
#RequestMapping(value={"/other"})
public ModelAndView otherPage() {
return new ModelAndView("other");
}
}
WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0" metadata-complete="false">
<servlet>
<servlet-name>ui</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<!-- spring automatically discovers /WEB-INF/<servlet-name>-servlet.xml -->
</servlet>
<servlet-mapping>
<servlet-name>ui</servlet-name>
<url-pattern>/ui/*</url-pattern>
</servlet-mapping>
</web-app>
WEB-INF/ui-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="controllers" />
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:order="2"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/views/"
p:suffix=".jsp"/>
</beans>
And also 2 JSP files at WEB-INF/views/index.jsp and WEB-INF/views/other.jsp.
Result:
http://localhost/myapp/ -> directory listing
http://localhost/myapp/ui and http://localhost/myapp/ui/ -> index.jsp
http://localhost/myapp/ui/other and http://localhost/myapp/ui/other/ -> other.jsp
Hope this helps!
PathMatchConfigurer api allows you to configure various settings
related to URL mapping and path matching. As per the latest version of spring, trail path matching is enabled by default. For customization, check the below example.
For Java-based configuration
#Configuration
#EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseTrailingSlashMatch(true);
}
}
For XML-based configuration
<mvc:annotation-driven>
<mvc:path-matching trailing-slash="true"/>
</mvc:annotation-driven>
For #RequestMapping("/foo"), if trailing slash match set to false, example.com/foo/ != example.com/foo and if it's set to true (default), example.com/foo/ == example.com/foo
Cheers!
I eventually added a new RequestMapping to redirect the /ui requests to /ui/.
Also removed the empty string mapping from the mainPage's RequestMapping.
No edit required to web.xml.
Ended up with something like this in my controller:
#RequestMapping(value="/ui")
public ModelAndView redirectToMainPage() {
return new ModelAndView("redirect:/ui/");
}
#RequestMapping(value="/")
public ModelAndView mainPage() {
DataModel model = initModel();
model.setView("intro");
return new ModelAndView("main", "model", model);
}
#RequestMapping(value={"/other"})
public ModelAndView otherPage() {
DataModel model = initModel();
model.setView("otherPage");
return new ModelAndView("other", "model", model);
}
Now the URL http://myhost/myapp/ui redirects to http://myhost/myapp/ui/ and then my controller displays the introductory page.
Another solution I found is to not give the request mapping for mainPage() a value:
#RequestMapping
public ModelAndView mainPage() {
DataModel model = initModel();
model.setView("intro");
return new ModelAndView("main", "model", model);
}
try adding
#RequestMapping(method = RequestMethod.GET)
public String list() {
return "redirect:/strategy/list";
}
the result:
#RequestMapping(value = "/strategy")
public class StrategyController {
static Logger logger = LoggerFactory.getLogger(StrategyController.class);
#Autowired
private StrategyService strategyService;
#Autowired
private MessageSource messageSource;
#RequestMapping(method = RequestMethod.GET)
public String list() {
return "redirect:/strategy/list";
}
#RequestMapping(value = {"/", "/list"}, method = RequestMethod.GET)
public String listOfStrategies(Model model) {
logger.info("IN: Strategy/list-GET");
List<Strategy> strategies = strategyService.getStrategies();
model.addAttribute("strategies", strategies);
// if there was an error in /add, we do not want to overwrite
// the existing strategy object containing the errors.
if (!model.containsAttribute("strategy")) {
logger.info("Adding Strategy object to model");
Strategy strategy = new Strategy();
model.addAttribute("strategy", strategy);
}
return "strategy-list";
}
** credits:
Advanced #RequestMapping tricks – Controller root and URI Template
Not sure if this is the ideal approach, but what worked for me was to treat them as if they were two different paths and make them both accepted by each of my endpoints, such as.
#RestController
#RequestMapping("/api/mb/actor")
public class ActorController {
#GetMapping({"", "/"})
public ResponseEntity<Object> getAllActors() {
...
}
#GetMapping({"/{actorId}", "/{actorId}/"})
public ResponseEntity<Object> getActor(#PathVariable UUID actorId) {
...
}
There may be best ways to do this and to avoid this duplication, and I'd love to know that. However, what I found when I tried using configurer.setUseTrailingSlashMatch(true); is that broken paths also start becoming accepted, such as /api/mb////actor (with many slashs), and that's why I ended up going the multiple paths instead.