I am making a SOAP call with using a Ruby On Rails gem Savon and when making a request, I get this error message:
Bad Request - Invalid Header
HTTP Error 400. The request has an invalid header name.
It sounds pretty descriptive, but I am still not able to figure out the problem. I also found a few topics here on SO, but none of them has unfortunately helped me.
Here's how I do make the connection:
client = Savon.client(endpoint: wsdl_url,
namespace: '',
env_namespace: :s,
basic_auth: ["username", "password"],
ssl_verify_mode: :none,
log: true,
logger: Rails.logger,
pretty_print_xml: true)
#response = client.call('MyAction',
soap_action: 'url address',
xml: xml_payload,
headers: {
'Content-Type': 'application/soap+xml; charset=utf-8',
})
And here's the generated error from the (terminal) console:
SOAP response (status 400)
<?xml version="1.0"?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<HTML>
<HEAD><TITLE>Bad Request</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"/>
<BODY><h2>Bad Request - Invalid Header</h2>
<hr><p>HTTP Error 400. The request has an invalid header name.</p>
</hr></BODY>
</HEAD>
</HTML>
If I change the header config from this:
#response = client.call('MyAction',
soap_action: 'url address',
xml: xml_payload,
headers: {
'Content-Type': 'application/soap+xml; charset=utf-8',
})
to this:
#response = client.call('MyAction',
soap_action: 'url address',
xml: xml_payload)
The error message says a lot less:
Savon::HTTPError - HTTP error (400):
So the header makes it a bit more descriptive. If I look at the headers generated by Savon, it looks like this:
SOAP request: https://URL_ENDPOINT
Content-Type: application/soap+xml; charset=utf-8, SOAPAction: "http://URL_ENDPOINT/MyAction", Content-Type: text/xml;charset=UTF-8, Content-Length: 2935
What headers are wrong? How do I debug/inspect it here?
I also tried to investigate this issue in Postman and got only this error message:
400 Bad Request - The request cannot be fulfilled due to bad syntax.
I also tried to validate the generated XML data I send to the server - and that's valid (verified on different validators).
How do I solve this problem?
Thank you
Related
I am doing an API automation in Ruby using the Faraday Gem to automate a file upload in my API.
I have the follow problem: I need to upload the file as form-data, but it is not working.
Here's the latest syntax that I'm using:
conn = Faraday.new($api['upload']) do |f|
f.request :multipart
f.adapter Faraday.default_adapter
end
formdata = { :file => Faraday::UploadIO.new('./arquivo/pequeno.pdf', 'file/pdf') }
headers = {'Content-Type' => 'multipart/form-data', 'Authorization' => 'Bearer ' + #token, 'uuidUser' => #uuid}
conn.post('/upload', formdata, headers)
Also, I cannot get the response body or code. I got the error that method body or code or status does not exist.
Do you have any idea what I may be doing wrong?
I put the the logger to see further what is happening, and this is the result:
W, [2019-11-27T11:15:15.385754 #4208] WARN -- : HTTP 500
D, [2019-11-27T11:15:15.386412 #4208] DEBUG -- : "x-content-type-options: nosniff\nx-xss-protection: 1; mode=block\ncache-control: no-cache, no-store, max-age=0, must-revalidate\npragma: no-cache\nexpires: 0\nx-frame-options: DENY\ncontent-type: application/json;charset=UTF-8\ntransfer-encoding: chunked\ndate: Wed, 27 Nov 2019 14:15:14 GMT\nconnection: close\n\n{\"exception\":\"org.springframework.web.multipart.MultipartException\",\"status\":500,\"error\":[\"Failed to parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found\"]}"
I solved the problem.
It was on the type of file. Instead of using 'file/pdf' I changed to 'application/pdf' and worked.
For each type of file there is a different way of usage:
pdf:
application/pdf
docx:
application/vnd.openxmlformats-officedocument.wordprocessingml.document
We're using invisible reCaptcha and, once in a while, Google's Javascript code makes a request to Google's servers receiving a response status 410, instead of 200.
We don't have control over it as the request is being made by Google's reCaptcha Javascript code.
If the challenge has been presented to the user, the following is an example of what the failing request looks like:
GET https://www.google.com/recaptcha/api2/payload?c=03AOPBWq_EYB...
And the response is:
HTTP/1.1 410 Gone
<HTML>
<HEAD>
<TITLE>Gone</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000">
<H1>Gone</H1>
<H2>Error 410</H2>
</BODY>
</HTML>
It fails about 10% of the time. Below is a "history" of HTTP requests:
Request #1
POST https://www.google.com/recaptcha/api2/reload?k=6LcqZCEU...
v:r20170515161201
reason:fi
bcr:[1943341955,-150...
...
HTTP/1.1 200 OK
content-type: application/json
...
)]}'
["rresp","03AOPBWq_EYBOYkGkn-1S...",null,600,["pmeta",null,null,null,
null,[[["TileSelectionStreetSign",null,3,4,4,null,null,[]
]
,["dress",null,3,4,4,null,null,[]
]
]
,[]
]
]
,"multicaptcha",null,
["bgdata","Ly93d3cuWk5rOHFMZDlvNDZFa..."]
]
The above response looks like invalid JSON but is expected as detailed here
Request #2
GET https://www.google.com/recaptcha/api2/payload?c=03AOPBWq_EYBOYk...
...
HTTP/1.1 200 OK
content-type: image/jpeg
content-length: 50528
...
<JPEG>
Request #3
POST https://www.google.com/recaptcha/api2/replaceimage?k=6LcqZCEUAA...
v:r20170515161201
c:03AOPBWq_EYBOYkGkn-1SplFL...
ds:[[5,6,9,10,13,14]]
HTTP/1.1 200 OK
content-type: application/json
...
)]}'
["dresp","03AOPBWq-Iyck5GCpx86hk57XSxF-9b4GMaDeujP...",[]
,null,[]
]
Request #4 (the failing one)
GET https://www.google.com/recaptcha/api2/payload?c=03AOPBWq_EYBOYk...
...
HTTP/1.1 410 Gone
content-type: text/html
...
<HTML>
<HEAD>
<TITLE>Gone</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000">
<H1>Gone</H1>
<H2>Error 410</H2>
</BODY>
</HTML>
This is basically because the session has expired. When ever you receive an error 410 (i.e. not successful and you still wish to upload the file), you must start a new session. Please have a look at the below link for details :
https://www.rfc-editor.org/rfc/rfc7231#section-6.5.9
Your call is GET. You need POST
Ref: https://www.google.com/recaptcha/api2/payload?c=03AOPBWq_EYB..
Note: I do not have enough reputation to post all the links, so I just removed the first letter so that the text editor would not read it as a link. To go to them please just paste it into your address bar and add an h in the front.
I am trying to access user data from Khan Academy using their API, however, this is the first time I have used their API or AJAX. So far I have managed to retrieve data on videos or exercises, but I am having trouble with using OAuth to retrieve user data. Here is info on Khan Academy authorization. They use OAuth 1.0. Here is the OAuth library I am using. Here is the documentation for the KA API: http://api-explorer.khanacademy.org/ I have tried:
var oauth = OAuth({
consumer: {
public: '****************',
secret: '****************'
},
signature_method: 'HMAC-SHA1'
});
var request_data = {
url: 'http://www.khanacademy.org/api/v1/exercises/logarithms_1',
method: 'GET'
};
$.ajax({
url: request_data.url,
type: request_data.method,
headers: oauth.toHeader(oauth.authorize(request_data))
}).done(function(data) {
alert("done");
});
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>KA API Test</title>
<meta http-equiv="Access-Control-Allow-Origin" content="*">
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<meta name="generator" content="Geany 1.23.1" />
<!-- sha1 -->
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/hmac-sha1.js"></script>
<!-- sha256 -->
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/hmac-sha256.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/enc-base64-min.js"></script>
<script src="oauth-1.0a.js"></script>
<script src="jquery-1.12.0.min.js"></script>
</head>
<body>
</body>
</html>
I have also tried this Javascript with the same HTML:
var oauth = OAuth({
consumer: {
public: 'qdMdMjjJQKrwJw2S',
secret: '7XeknfpVBzx8fGMK'
},
signature_method: 'HMAC-SHA1'
});
var request_data = {
url: 'http://www.khanacademy.org/api/v1/exercises/logarithms_1',
method: 'GET'
};
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.khanacademy.org/api/v1/exercises/logarithms_1", true);
xhr.setRequestHeader("Authorization", oauth.toHeader(oauth.authorize(request_data)));
xhr.send();
xhr.addEventListener("readystatechange", processRequest, false);
function processRequest(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
var response = JSON.parse(xhr.responseText);
for (var key in response) {
console.log(key + ":" + response[key]);
}
alert(response.translated_description_html);
}
}
Both give this error in the console: "XMLHttpRequest cannot load http://www.khanacademy.org/api/v1/exercises/logarithms_1. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access."
Note: The XMLHttpRequest works fine and returns what I want before adding xhr.setRequestHeader("Authorization", "OAuth " + oauth.toHeader(oauth.authorize(request_data))); to it. However, that is just a test link which does not require authorization and I will change it later.
P.S. I would like to fix this error, but I also just need to use OAuth correctly and I think they are related because I did not have the error before using OAuth.
The problem is with Cross Domain Access Origins. You need to ask the API Owner to allow Cross Domains Origin.
Access-Control-Allow-Origin is a CORS (Cross-Origin Resource Sharing) header.
When Site A tries to fetch content from Site B, Site B can send an Access-Control-Allow-Origin response header to tell the browser that the content of this page is accessible to certain origins. (An origin is a domain, plus a scheme and port number.) By default, Site B's pages are not accessible to any other origin; using the Access-Control-Allow-Origin header opens a door for cross-origin access by specific requesting origins.
For each resource/page that Site B wants to make accessible to Site A, Site B should serve its pages with the response header:
Access-Control-Allow-Origin: http:// siteA.com
Modern browsers will not block cross-domain requests outright. If Site A requests a page from Site B, the browser will actually fetch the requested page on the network level and check if the response headers list Site A as a permitted requester domain. If Site B has not indicated that Site A is allowed to access this page, the browser will trigger the XMLHttpRequest's error event and deny the response data to the requesting JavaScript code.
Non-simple requests
What happens on the network level can be slightly more complex than explained above. If the request is a "non-simple" request, the browser first sends a data-less "preflight" OPTIONS request, to verify that the server will accept the request. A request is non-simple when either (or both):
using an HTTP verb other than GET or POST (e.g. PUT, DELETE)
using non-simple request headers; the only simple requests headers are:
Accept
Accept-Language
Content-Language
Content-Type (this is only simple when its value is application/x-www-form-urlencoded, multipart/form-data, or text/plain)
If the server responds to the OPTIONS preflight with appropriate response headers (Access-Control-Allow-Headers for non-simple headers, Access-Control-Allow-Methods for non-simple verbs) that match the non-simple verb and/or non-simple headers, then the browser sends the actual request.
Supposing that Site A wants to send a PUT request for /somePage, with a non-simple Content-Type value of application/json, the browser would first send a preflight request:
OPTIONS /somePage HTTP/1.1
Origin: http://siteA.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
Note that Access-Control-Request-Method and Access-Control-Request-Headers are added by the browser automatically; you do not need to add them. This OPTIONS preflight gets the successful response headers:
Access-Control-Allow-Origin: http:// siteA.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type
When sending the actual request (after preflight is done), the behavior is identical to how a simple request is handled. In other words, a non-simple request whose preflight is successful is treated the same as a simple request (i.e., the server must still send Access-Control-Allow-Origin again for the actual response).
The browsers sends the actual request:
PUT /somePage HTTP/1.1
Origin: http:// siteA.com
Content-Type: application/json
{ "myRequestContent": "Response in JSON" }
And the server sends back an Access-Control-Allow-Origin, just as it would for a simple request:
Access-Control-Allow-Origin: http:// siteA.com
See Understanding XMLHttpRequest over CORS for a little more information about non-simple requests.
I'm trying to use Savon to make a SOAP request with Ruby, but I'm receiving a 400 Bad Request response from the server.
This is the request I'm trying to make (according to soapUI):
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:apis="http://www.csisoftwareusa.com/ApiService">
<soap:Header/>
<soap:Body>
<apis:AuthenticateConsumer>
<!--Optional:-->
<apis:consumerName>?</apis:consumerName>
<!--Optional:-->
<apis:consumerPassword>?</apis:consumerPassword>
</apis:AuthenticateConsumer>
</soap:Body>
</soap:Envelope>
Here is the request that I make with Ruby; it returns a 400 Bad Request error:
<SOAP-ENV:Envelope>
<SOAP-ENV:Body>
<ins0:AuthenticateConsumer>
<ins0:consumerName>?</ins0:consumerName>
<ins0:consumerPassword>?</ins0:consumerPassword>
</ins0:AuthenticateConsumer>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Http Headers: SOAPAction: "http://www.csisoftwareusa.com/ApiService/AuthenticateConsumer", Content-Type: text/xml;charset=UTF-8, Content-Length: 504
Here is the request that I was able to make with Python. THIS request succeeds:
<SOAP-ENV:Envelope>
<SOAP-ENV:Header/>
<ns0:Body>
<ns1:AuthenticateConsumer>
<ns1:consumerName>?</ns1:consumerName>
<ns1:consumerPassword>?</ns1:consumerPassword>
</ns1:AuthenticateConsumer>
</ns0:Body>
</SOAP-ENV:Envelope>
Http headers: {'SOAPAction': u'"http://www.csisoftwareusa.com/ApiService/AuthenticateConsumer"', 'Content-Type': 'text/xml; charset=utf-8'}
I need to integrate calls to this API into a Rails application, so doing it in Python isn't a valid solution.
I'm wondering if anyone can see what I'm missing. Is the empty <SOAP-ENV:Header /> tag the issue, and if so, how can I add that to the Savon request?
Thanks,
Stuart
In my case, I had repeated namespaces, so I was getting 400 Bad Request.
My code:
require 'savon'
namespaces = {
"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",
}
client = Savon.client(
...
:namespace => namespaces
}
I removed the :namespace option and the error was gone.
How did I find the error?
Use build_request instead of call and print the request body:
client.build_request(:search, message: {...})
puts request.body
I took the request body and pasted it into SoapUI, then I made changes one by one until the request was successful.
The issue here is with my http headers: Because the url has spaces in it, the url has to be encoded. However, I have to encode it before passing it to Savon, which then encodes it again - this double-encoded url fails. See: https://stackoverflow.com/questions/14482251/ruby-savon-soap-request-double-escapes-spaces-in-url
Thanks,
Stuart
I am trying to make a SOAP request using the ruby library Savon.
I am using the following code:
require "savon"
Savon.configure do |config|
config.soap_version = 2 # use SOAP 1.2
config.raise_errors = false
end
wsdl_logon = Savon::Client.new do
wsdl.document = "https://api.affili.net/V2.0/Logon.svc?wsdl"
end
username = 'XXX'
password = 'YYY'
wsdl_logon.http.headers["Content-Type"] = "text/xml; charset=utf-8"
response = wsdl_logon.request "Logon" do
soap.body = {'Username' => username, 'Password' => password, 'WebServiceType' => 'Product'}
end
if response.http_error?
puts "Http Error!"
puts y response.http_error
else
puts "No Http Error!"
end
But I keep receiving 400 error messages ("bad request"). Or, if I remove the following line
wsdl_logon.http.headers["Content-Type"] = "text/xml; charset=utf-8"
I am receiving 415 error messages ("unsupported media type").
I have been using PHP to make these requests until now, and the following code always worked without problems:
$soap_logon = new SoapClient('https://api.affili.net/V2.0/Logon.svc?wsdl');
$token = $soap_logon->Logon(array(
'Username' => 'XXX',
'Password' => 'YYY',
'WebServiceType' => 'Product'
));
Can anybody point me to the right direction what a possible error source might be? I am completely lost right now.
Thank you for your help.
I did as Tom De Leu suggested, and tried to remove as many differences in the generated SOAP requests in question as possible. But I still keep receiving 400 errors. Any hint on possible reasons for this would be highly appreciated.
This is the (working) Request generated by PHP (linebreaks in XML added for clarity):
POST /V2.0/Logon.svc HTTP/1.1
Host: api.affili.net
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.2.0-8+etch16
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://affilinet.framework.webservices/Svc/ServiceContract1/Logon"
Content-Length: 456
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://affilinet.framework.webservices/types"
xmlns:ns2="http://affilinet.framework.webservices/Svc"
>
<SOAP-ENV:Body>
<ns2:LogonRequestMsg>
<ns1:Username>xxx</ns1:Username>
<ns1:Password>yyy</ns1:Password>
<ns1:WebServiceType>Product</ns1:WebServiceType>
</ns2:LogonRequestMsg>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
This is the (not working) request generated by Ruby (again, xml linebreaks added for clarity)
SOAP request: https://api.affili.net/V2.0/Logon.svc
Content-Type: text/xml; charset=utf-8, SOAPAction: http://affilinet.framework.webservices/Svc/ServiceContract1/Logon, Content-Length: 605
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:wsdl="http://affilinet.framework.webservices/Svc"
SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://affilinet.framework.webservices/types"
xmlns:ns2="http://affilinet.framework.webservices/Svc"
>
<SOAP-ENV:Body>
<ns2:LogonRequestMsg>
<ns1:Username>XXX</ns1:Username>
<ns1:Password>YYY</ns1:Password>
<wsdl:WebServiceType>Product</wsdl:WebServiceType>
</ns2:LogonRequestMsg>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
HTTPI executes HTTP POST using the httpclient adapter
SOAP response (status 400):
I found that I needed to add the headers in to get past the 415 error.
Savon.client(wsdl: "www.sample_doman.com/endpoint?wsdl", headers: {'Content-Type' => 'application/soap+xml; charset=utf-8'})
I would suggest looking at the XML sent by the PHP code, then comparing it with the XML sent by the Ruby Savon code, and check where the differences are. Then see whether you can modify your ruby code to generate the correct request.
Telling Savon to use SOAP version 2 (really 1.2) and then manually setting the content type to text/xml kind of defeats the purpose.
If your web service requires SOAP 1.2, then it is expecting a content type of 'application/soap+xml', which SAVON will do for you if you set the soap_version to 2.
If you want a content type of text/xml, just set your soap_version config variable to 1