I need to log the incomming REST request (xml).
So I create a
public class RequestWrapper extends HttpServletRequestWrapper {
private String _body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
_body = "";
BufferedReader bufferedReader = request.getReader();
String line;
while ((line = bufferedReader.readLine()) != null){
_body += line;
}
}
Now I use a filter to log out the incomming request:
class XmlogFilterFilters {
def filters = {
all(controller:'*', action:'*') {
before = {
RequestWrapper wrappedRequest = new RequestWrapper(request)
log.info(wrappedRequest.reader.text)
}
}
}
This logs the incomming request as expected.
But now in my controller the request is empty and cannot be used to Build my Domain-Object:
class InquiryHandlerController {
def save(Inquiry inquiryInstance) {
... *** inquiryInstance is null here
}
}
I guess the problem is, that th request was already read in the RequestWrapper, and therefor cannot be read again in the magic requestToDomain conversion.
So how can I pass the new RequestWrapper Object instead of the original request to the controller?
Finally found a solution:
use grailsplugin: grails-httplogger in version 1.1 do the right wrapper thing, so logging and consuming is working now.
https://github.com/prumps/grails-httplogger
Related
Amazon provides a vast documentation, but there are so many docs that I'm lost, so here is my current service for upload/download files. Upload works as expected but on the download its where I have to download the files to a physical path and later serve the download to the user, I don't have much experience working with streams. Here is the FileManagerService class that connects to Amazon API.
using Amazon.S3;
using Amazon.S3.Model;
public class FileManagerService
{
public FileManagerService()
{
string serverPath = HttpContext.Current.Server.MapPath("~/");
string uploadPath = Path.Combine(serverPath, "FileUploads");
Directory.CreateDirectory(uploadPath);
UploadDirectory = uploadPath;
}
private string UploadDirectory { get; set; }
private docucloudEntities db = new docucloudEntities();
private IAmazonS3 S3Client = new AmazonS3Client();
private string S3Bucket = "bucketname";
public async Task<string> DownloadFile(string AmazonFileKey, string FileName)
{
var fileRequest = new GetObjectRequest
{
BucketName = S3Bucket,
Key = AmazonFileKey
};
var localRoute = Path.Combine(UploadDirectory, FileName);
using (var fileObject = await S3Client.GetObjectAsync(fileRequest))
{
if (fileObject.HttpStatusCode == HttpStatusCode.OK)
{
fileObject.WriteResponseStreamToFile(localRoute);
}
}
return localRoute;
}
}
This method returns the string, it's not complete yet with try catch blocks, but it currently works. Here is my controller method that download the file to the client:
public class FileManagerController : Controller
{
private FileManagerService FileService = new FileManagerService();
public async Task<ActionResult> DownloadFileAmazon(long FileId)
{
if (db.Archivos.Any(i => i.ArchivoID == FileId))
{
var archivo = db.Archivos.Single(i => i.ArchivoID == FileId);
var rutaarchivo = await FileService.DownloadFile(archivo.Ruta, archivo.Nombre);
if (System.IO.File.Exists(rutaarchivo))
{
var fileBytes = System.IO.File.ReadAllBytes(rutaarchivo);
var response = new FileContentResult(fileBytes, "application/octet-stream");
response.FileDownloadName = archivo.Nombre;
System.IO.File.Delete(rutaarchivo);
return response;
}else
{
return HttpNotFound();
}
}else
{
return HttpNotFound();
}
}
}
So here on the controller I read the file bytes and serve the download, after deleting the file, but this could lead to a slower perfomance, its there a way of achieving direct download.
As far as I can tell there is no reason to dispose GetObjectResponse (return type of GetObjectAsync) even if the docs says so. GetObjectResponse is not implementing IDisposable but is inheriting StreamResponse that is. However, as far as I can tell it's only disposing the ResponseStream. So you could return the stream from GetObjectResponse (fileObject.ResponseStream) together with the ContentTypefrom the headers (fileObject.Headers.ContentType) that you then can return as a file in your controller:
[HttpGet]
[Route("blob/{filename}")]
public async Task<IActionResult> GetFile(string filename)
{
try
{
var file = await _fileStorageService.GetBlobAsync(filename);
return File(file.Stream, file.ContentType);
}
catch (Exception ex)
{
// Handle exceptions
}
}
FileResult will dispose the stream after it has written the file so there the stream will finally get disposed.
I need to add header to a STOMP message currently it is working as below but i am recreating the message , is it possible to just add native header without having to recreate the message for performance .
public class MyChannelInterceptor extends ChannelInterceptorAdapter {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
StompCommand command = accessor.getCommand();
if(command != null) {
log.debug("Receiving msg {} from {}",command,accessor.getUser().getName());
if(command == StompCommand.SEND) {
log.debug("Adding expires header to msg {} from {}",command,accessor.getUser().getName());
String ttlString = accessor.getFirstNativeHeader("ttl");
long ttl = 30000;
try {
ttl = Long.parseLong(ttlString);
}
catch(Exception ex) {
log.error("TTL header received but not in correct format {}",ttlString);
}
accessor.addNativeHeader("expires", Long.toString(System.currentTimeMillis() + ttl));
return MessageBuilder.createMessage(message.getPayload(), accessor.getMessageHeaders());
}
}
return message;
}
}
This is what i was looking for
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
The above code will get the actual StompHeaderAccessor of the message so if you manipulate the native headers they are directly reflected on the message while
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
will get a clone of the headers and you have to create a new message with the new cloned headers
full fixed code below
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
// StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
if(accessor != null) {
StompCommand command = accessor.getCommand();
if(command != null) {
log.debug("Receiving msg {} from {}",command,accessor.getUser().getName());
if(command == StompCommand.SEND) {
log.debug("Adding expires header to msg {} from {}",command,accessor.getUser().getName());
String ttlString = accessor.getFirstNativeHeader("ttl");
long ttl = 30000;
if(ttlString != null) {
try {
ttl = Long.parseLong(ttlString);
}
catch(Exception ex) {
log.error("TTL header received but not in correct format {}",ttlString);
}
}
accessor.addNativeHeader("expires", Long.toString(System.currentTimeMillis() + ttl));
// I don't need any more to create a new message
//return MessageBuilder.createMessage(message.getPayload(), accessor.getMessageHeaders());
}
}
}
return message;
}
Since addNativeHeader succeeds, that indicates the message is still mutable - see addNativeHeader().
In any case, since the NATIVE_HEADERS message header is a MultiValueMap-valued header, you can update the header contents in-place.
Hence, there is no need to create a new message.
You would have to create a new message if you add a new header to the message itself (rather than updating the mutable contents of an existing header).
EDIT
I just ran a test; as long as the message is still mutable, you can change it...
#Test
public void test() {
Map<String, Object> map = new HashMap<String, Object>();
MutableMessageHeaders headers = new MutableMessageHeaders(map);
Message<String> message = MessageBuilder.createMessage("foo", headers);
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
accessor.addNativeHeader("foo", "bar");
System.out.println(message.getHeaders().get(NativeMessageHeaderAccessor.NATIVE_HEADERS));
accessor.setImmutable();
try {
accessor.addNativeHeader("baz", "qux");
fail("expected IllegalStateException");
}
catch (IllegalStateException e) {
}
}
That said, are you experiencing a performance problem or is this just a perceived issue. Message creation is not expensive.
I am consuming someone elses REST service for my app. the problem is that each request can return 1 of 3 different types when responding
either:
the expected successfull response type
an error response which wraps the 500 (Error)
a validation error response (ValidationErrors)
I am currently calling the service wrapping each request with a class like this:
public class ApiResponse<T>
{
public T ResponseObject { get; set; }
public ValidationErrors<ValidationError> Errors { get; set; }
public Error Error { get; set; }
}
public async Task<ApiResponse<AMethodResponse>> AMethod(AMethodRequest req)
{
ApiResponse<AMethodResponse> resp = new ApiResponse<AMethodResponse> { Errors = new ValidationErrors<ValidationError>() };
using (HttpClient client = HttpClientFactory.Create(new AuthorisationHandler(), new ContentTypeHandler()))
{
client.BaseAddress = new Uri(BaseURI);
var httpResponseMessage = await client.PostAsXmlAsync<AMethodRequest>("AMethod/", req);
if (!httpResponseMessage.IsSuccessStatusCode)
{
//its at this point that I need to work out if i am getting Validation Errors or.. a plain Error
//I can do this, but of course if its a plain error it will fall over
resp.Errors = await httpResponseMessage.Content.ReadAsAsync<ValidationErrors<ValidationError>>();
}
else
{
resp.ResponseObject = await httpResponseMessage.Content.ReadAsAsync<AMethodResponse>();
}
}
return resp;
}
I wonder if there is a more reliable pattern to writing consuming methods.
thanks
it gives a 200 for all is well. 400 for validationerror and 500 for a real error
Check the status code directly, rather than use IsSuccessStatusCode :
var httpResponseMessage = await client.PostAsXmlAsync<AMethodRequest>("AMethod/", req);
switch (httpResponseMessage.StatusCode)
{
case HttpStatusCode.OK: //200
resp.ResponseObject = await httpResponseMessage.Content.ReadAsAsync<AMethodResponse>();
break;
case HttpStatusCode.BadRequest: //400
resp.Errors = await httpResponseMessage.Content.ReadAsAsync<ValidationErrors<ValidationError>>();
break;
case HttpStatusCode.InternalServerError: //500
throw new Exception("failed"); // use appropriate exception and/or read 500 wrapper
break;
}
return resp;
I have tokenValiditySeconds set in the Config.groovy as
grails.plugins.springsecurity.rememberMe.tokenValiditySeconds=31*24*60*60
However I want to set a different validity for all requests that comes from, say a sub-domain. I can identify domain info from the request object, but I am not able to override the tokenValiditySeconds from the CustomRememberMeService class.
By default the tokens will be valid for 14 days from the last
successful authentication attempt. This can be changed using
AbstractRememberMeServices.setTokenValiditySeconds(int). If this value
is less than zero, the expiryTime will remain at 14 days, but the
negative value will be used for the maxAge property of the cookie,
meaning that it will not be stored when the browser is closed.
As per the documentation, I should be able to change the validity by using setTokenValiditySeconds(int) method but it does not have any effect.
So how to override the value set in the config file?
Thanks.
Edit:
class CustomRememberMeService extends TokenBasedRememberMeServices {
def springSecurityService;
public final LoggedInUserDetails customAutoLogin(HttpServletRequest request, HttpServletResponse response) {
def cookies = request.getCookies();
if (!cookies) return null;
String rememberMeCookie = extractRememberMeCookie(request);
for (int i = 0; i < cookies.length; i++) {
Cookie c = cookies[i];
if(c.getName().equals('remember_me') && rememberMeCookie == null) {
rememberMeCookie = c.getValue();
}
}
if (rememberMeCookie == null) return null
logger.debug("rememberMeCookie is : ${rememberMeCookie}");
if (rememberMeCookie.length() == 0) {
cancelCookie(request, response);
return null;
}
String[] cookieTokens = decodeCookie(rememberMeCookie);
String username = cookieTokens[0];
def loginContext = request.getParameter('loginContext')
loginContext = (loginContext == null) ? "mainWeb" : loginContext
setTokenValiditySeconds(60) // not working
LoggedInUserDetails user = getUserDetailsService().loadUserByUsername("${username}#${request.getServerName().trim()}#${loginContext}")
springSecurityService.reauthenticate("${username}#${request.getServerName().trim()}#${loginContext}")
}
}
The resource.groovy file looks like:
//..
customRememberMeService(com.rwi.springsecurity.services.CustomRememberMeService) {
userDetailsService = ref('userDetailsService')
springSecurityService = ref('springSecurityService')
key = "${grailsApplication.config.grails.plugins.springsecurity.rememberMe.key}"
}
customRememberMeServicesFilter(CustomRememberMeServicesFilter){
authenticationManager = ref('authenticationManager')
rememberMeServices = ref('rememberMeServices')
customRememberMeService = ref('customRememberMeService')
}
//..
CustomRemeberMEService.groovy
// ..
class CustomRememberMeServicesFilter extends RememberMeAuthenticationFilter {
def customRememberMeService;
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (SecurityContextHolder.getContext().getAuthentication() == null) {
LoggedInUserDetails rememberMeAuth = customRememberMeService.customAutoLogin(request, response);
}
chain.doFilter(request, response);
}
}
Override the method calculateLoginLifetime, by default this will return the value as set in the configuration (it calls getTokenValiditySeconds(). By overriding this you can determine (based on the request) if the normal timeout should be passed or a custom one.
protected int calculateLoginLifetime(HttpServletRequest request, Authentication authentication) {
if (request.getRemoteAddr().startsWith("subdomain") {
return 15; // Or whatever you want, you could also make it configurable.
}
return getTokenValiditySeconds();
}
I try sing and encrypt SOAP message in ASP.NET Web Service.
//I have Crypt class, which input parameters is Stream:
public class CryptUtility
{
public virtual Stream EncryptAndSingXml (Stream inputStream)
{
XmlTextReader reader = new XmlTextReader(inputStream);
XmlDocument doc = new XmlDocument();
doc.Load(reader);
// in this place I encrypt and sign SOAP message
foreach (string xPathQuery in soapElement)
{
XmlNodeList nodesToEncrypt = doc.SelectNodes(xPathQuery, nsMan);
foreach (XmlNode nodeToEncrypt in nodesToEncrypt)
{
// method EncryptString crypt only string from XmlNode
nodeToEncrypt.InnerXml = EncryptString();
}
}
// !!!
// I THINK HERE IS A PROBLEM
//
//it return plain stream, no encrypt stream
MemoryStream retStream = new MemoryStream();
XmlTextWriter writer = new XmlTextWriter(retStream, Encoding.UTF8);
doc.Save(retStream);
return retStream;
}
}
I used CryptUtility object in Soap extension class:
public class SoapMsg : SoapExtension
{
private CryptUtility cryptUtil; //this object crypt and sing SOAP message
// ...
//this method copy stream
private void CopyStream(Stream from, Stream to)
{
TextReader reader = new StreamReader(from);
TextWriter writer = new StreamWriter(to);
writer.Write(reader.ReadToEnd());
writer.Flush();
}
//this method sing and encrypt SOAP message, I call this method in stage AfterSerialize
private void CryptMessage()
{
newStream.Position = 0;
Stream retStream = cryptUtil.EncryptAndSingXml(newStream);
retStream.Position = 0;
CopyStream(retStream, oldStream);
}
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
{
// call the crypt and sing method
CryptMessage();
//save the SOAP message, the message is not encrypt
Log(message, "AfterSerialize");
}
break;
case SoapMessageStage.BeforeDeserialize:
break;
case SoapMessageStage.AfterDeserialize:
break;
default:
throw new ArgumentException("error.");
}
}
// ...
}
Problem is, when I log SOAP message in AfterDeserialize the XML is plain text, but it should be encrypt
Can somebody help me, where can be problem , or what can I do bad?
Because first I use method EncryptAndSingXml as void in class SoapMsg, and it work correct!!!
Something like this :
public class SoapMsg : SoapExtension
{
//...
public void EncryptAndSingXml()
{...}
//...
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
//...
case SoapMessageStage.AfterSerialize:
EncryptAndSingXml();
break;
//...
}
// ...
}
But when I makec class CryptUtility and method EncryptAndSingXml() as virtual it doesn't work. :(
Can somebody help me ?
It doesn't look like you are modifying message at all!
And, please show where you create an instance of CryptUtility.
Also, your code is pretty bad. Try this:
public class CryptUtility
{
public virtual Stream EncryptAndSingXml(Stream inputStream, IEnumerable<string> soapElement)
{
XmlDocument doc = new XmlDocument();
using (XmlReader reader = XmlReader.Create(inputStream))
{
doc.Load(reader);
}
// in this place I encrypt and sign SOAP message
XmlNamespaceManager nsMan = new XmlNamespaceManager(doc.NameTable);
foreach (string xPathQuery in soapElement)
{
XmlNodeList nodesToEncrypt = doc.SelectNodes(xPathQuery, nsMan);
foreach (XmlNode nodeToEncrypt in nodesToEncrypt)
{
// method EncryptString crypt only string from XmlNode
nodeToEncrypt.InnerXml = EncryptString(nodeToEncrypt.InnerXml);
}
}
// !!!
// I THINK HERE IS A PROBLEM
//
//it return plain stream, no encrypt stream
using (MemoryStream retStream = new MemoryStream())
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Encoding = Encoding.UTF8;
using (XmlWriter writer = XmlWriter.Create(retStream, settings))
{
doc.WriteTo(writer);
}
return retStream;
}
}
}
and
public class SoapMsg : SoapExtension
{
private CryptUtility cryptUtil; //this object crypt and sing SOAP message
// ...
//this method copy stream
private void CopyStream(Stream from, Stream to)
{
using (TextReader reader = new StreamReader(from))
{
using (TextWriter writer = new StreamWriter(to))
{
writer.Write(reader.ReadToEnd());
writer.Flush();
}
}
}
//this method sing and encrypt SOAP message, I call this method in stage AfterSerialize
private void CryptMessage()
{
newStream.Position = 0;
using (Stream retStream = cryptUtil.EncryptAndSingXml(newStream))
{
retStream.Position = 0;
CopyStream(retStream, oldStream);
}
}
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
{
// call the crypt and sing method
CryptMessage();
//save the SOAP message, the message is not encrypt
Log(message, "AfterSerialize");
}
break;
case SoapMessageStage.BeforeDeserialize:
break;
case SoapMessageStage.AfterDeserialize:
break;
default:
throw new ArgumentException("error.");
}
}
// ...
}