How to serve an image with Scalatra - scalatra

Is any version of Scalatra capable of serving images with the correct Content-Type header? I can't seem to suppress the automatic addition of charset.
Example of returning a File:
class MyServlet extends org.scalatra.ScalatraServlet {
get("/") {
contentType = "image/png"
new java.io.File("/tmp/test.png")
}
}
$ curl --head localhost:8080
HTTP/1.1 200 OK
Date: Thu, 01 Aug 2019 21:43:28 GMT
Content-Type: image/png;charset=utf-8
Transfer-Encoding: chunked
Server: Jetty(9.4.8.v20171121)
Example of returning an InputStream:
class MyServlet extends org.scalatra.ScalatraServlet {
get("/") {
contentType = "image/png"
new java.io.FileInputStream(new java.io.File("/tmp/test.png"))
}
}
$ curl --head localhost:8080
HTTP/1.1 200 OK
Date: Thu, 01 Aug 2019 21:49:52 GMT
Content-Type: image/png;charset=utf-8
Content-Length: 2881
Server: Jetty(9.4.8.v20171121)

One possible solution is setting ContentType and then writing binary directory to HttpServletResponse like this:
get("/image") {
contentType = "image/png"
val out = response.getOutputStream
// write binary to OutputStream here
...
// Prevent updating response headers in following processes
response.flushBuffer()
() // return Unit
}
By the way, I tried fixing this problem several years ago, but it has been reverted due to the backwards compatibility: https://github.com/scalatra/scalatra/pull/332

Related

Empty Body in Vapor Client GET Response

I'm trying to connect to
Bamboo HR's API, and I've managed to make this work with curl and Swift Foundation's URLRequest/URLSession (as well as an older Express node.js app).
However, when trying to utilize Vapor's client with the .get() method, I'm getting a successful 200 response from BambooHR - but the response's .body is empty.
Here's the code snippet:
key and {myDomain} are placeholders in the example
let encodedKey = "\(key):x".utf8.base64String
let directoryRootUrl = "https://api.bamboohr.com/api/gateway.php/{myDomain}/v1/employees/directory"
let response = try drop.client.get(
directoryRootURL,
headers: [
"Accept": "application/json",
"Authorization": "Basic \(encodedKey)",
"Host": "api.bamboohr.com"
])
When I do print(response), this is what's displayed:
Response
- HTTP/1.0 200 OK
- Headers:
Connection: close
Vary: User-Agent
Server: Apache
Content-Security-Policy: {...}
Date: Mon, 16 Jan 2017 00:26:31 GMT
Content-Type: application/json
X-Content-Type-Options: nosniff
- Body:
I'm wondering if I'm doing anything wrong with Vapor, or if it's a bug.
Like tobygriffin suggested, setting:
drop.client = FoundationClient.self
after creating the Droplet worked.

webserver based on node.js couldn't parse the right data which sent by method 'POST' via CURL

I implemented a simple webserver based on node.js, which handle the data
sent by method 'POST' via libCURL.
Client: c++ code based on libCURL
Server: Node.js
Result:
Client
I implemented a simple webserver based on node.js, which handle the data
sent by method 'POST' via libCURL.
Client: c++ code based on libCURL
Server: Node.js
###Client####
>>>
* Found bundle for host 109.123.121.146: 0x1b13f00 [can pipeline]
* Re-using existing connection! (#0) with host 109.123.121.146
* Connected to 109.123.121.146 (109.123.121.146) port 8124 (#0)
> POST / HTTP/1.1
Host: 109.123.121.146:8124
Accept: */*
Content-Type: text/xml
Content-Length: 11
* upload completely sent off: 11 out of 11 bytes
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Content-Type: text/plain;charset=utf-8
Content-Type: text/plain;charset=utf-8
< Date: Thu, 05 Jan 2017 21:14:05 GMT
Date: Thu, 05 Jan 2017 21:14:05 GMT
< Connection: keep-alive
Connection: keep-alive
< Transfer-Encoding: chunked
Transfer-Encoding: chunked
<
* Connection #0 to host 109.123.121.146 left intact
###Server####
####### 'POST' ######
Partial body: B=~� size:11
Body: B=~� size:11 ---->> why the data is invalid char, size is right.
Source Code
Client.cpp
string strUrl = "http://109.123.121.146:8124/";
string strLog = "Hello,CURL!";
curl_easy_setopt(g_curl_handle, CURLOPT_URL, strUrl.c_str());
curl_easy_setopt(g_curl_handle, CURLOPT_POST, 1);
curl_easy_setopt(g_curl_handle, CURLOPT_POSTFIELDS, strLog.c_str());
Server.js
req.addListener('data', function (data) {
body += data;
console.log("Partial body: " + data + " size:"+data.toString().length);
});
req.addListener('end', function () {
console.log("Body: " + body + " size:"+body.toString().length);
});
I wonder why the request data is invalid char:"B=~�" , or not the right string "Hello,CURL!",
Anyone konws, help to explain to me, thanks ~~~

helloworld on Kie Workbench and Kie Server version 6.3.0

I could setup KIE Execution Server (6.3.0.Final) and Workbench (6.3.0.Final) on Wildfly 8.1.0.Final using information available in blog:
http://mswiderski.blogspot.in/2015/10/installing-kie-server-and-workbench-on.html
Both server and Workbench are working fine and Server is visible in Workbench under "Server Management Browser" tab.
My next step is to deploy a simple container on server and test REST GET and POST calls, hence I followed steps mentioned in Question: HelloWorld using Drools Workbench & KIE Server
The only change in Java and DRL code is with respect to package. Below is my Java code:
package test.myproject;
/**
* This class was automatically generated by the data modeler tool.
*/
public class HelloWorld implements java.io.Serializable
{
static final long serialVersionUID = 1L;
private java.lang.String message;
public HelloWorld()
{
}
public java.lang.String getMessage()
{
return this.message;
}
public void setMessage(java.lang.String message)
{
this.message = message;
}
public HelloWorld(java.lang.String message)
{
this.message = message;
}
}
DRL file code:
package test.myproject;
import test.myproject.HelloWorld;
rule "hello"
when
HelloWorld(message == "Joe");
then
System.out.println("Hello Joe!");
end
The code is built successfully and deployed as container on the server. A GET Query using RESTClient/PostMan/Advanced REST Client gives proper response.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response type="SUCCESS" msg="Info for container myproject">
<kie-container container-id="myproject" status="STARTED">
<release-id>
<artifact-id>MyProject</artifact-id>
<group-id>test</group-id>
<version>1.0</version>
</release-id>
<resolved-release-id>
<artifact-id>MyProject</artifact-id>
<group-id>test</group-id>
<version>1.0</version>
</resolved-release-id>
<scanner status="DISPOSED"/>
</kie-container>
</response>
However When I POST to container with below content:
<batch-execution lookup="defaultKieSession">
<insert out-identifier="message" return-object="true" entrypoint="DEFAULT">
<test.myproject.HelloWorld>
<message>Joe</message>
</test.myproject.HelloWorld>
</insert>
Note that I did make changes to XML as per change in code. I did try with different options like etc, but I get response as :
Status Code: 405 Method Not Allowed
Allow: HEAD, DELETE, GET, OPTIONS, PUT
Cache-Control: no-cache, no-store, must-revalidate
Connection: keep-alive
Content-Length: 0
Date: Thu, 10 Dec 2015 05:29:09 GMT
Expires: 0
Pragma: no-cache
Server: WildFly/8
X-Powered-By: Undertow/1
Looks like POST option is not allowed, Hence tried PUT but got response as:
Status Code: 415 Unsupported Media Type
Cache-Control: no-cache, no-store, must-revalidate
Connection: keep-alive
Content-Length: 0
Date: Thu, 10 Dec 2015 05:32:17 GMT
Expires: 0
Pragma: no-cache
Server: WildFly/8
X-Powered-By: Undertow/1
Can anyone tell me where I am going wrong. I also checked log file and see below error when POST is called:
2015-12-10 10:59:09,208 WARN [org.jboss.resteasy.core.ExceptionHandler] (default task-48) failed to execute: javax.ws.rs.NotAllowedException: No resource method found for POST, return 405 with Allow header
at org.jboss.resteasy.core.registry.SegmentNode.match(SegmentNode.java:375) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.core.registry.SegmentNode.match(SegmentNode.java:114) [resteasy-jaxrs-3.0.8.Final.jar:]
and error when PUT is called:
2015-12-10 11:02:17,127 WARN [org.jboss.resteasy.core.ExceptionHandler] (default task-50) failed to execute: javax.ws.rs.NotSupportedException: Cannot consume content type
at org.jboss.resteasy.core.registry.SegmentNode.match(SegmentNode.java:380) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.core.registry.SegmentNode.match(SegmentNode.java:114) [resteasy-jaxrs-3.0.8.Final.jar:]
It is working now. Changes needed are as follows:
URL to call during POST is : http://localhost:8080/kie-server/services/rest/server/containers/instances/myproject
From 6.3.0 onward instances needs to be used (Make note of it :))
KIE Server in 6.3.0 supports JAXB, JSON and Xstream. Since default is JAXB you would need to provide JAXB valid content. Alternatively you could set HTTP header to inform KIE Server to use Xstream as marshaller:
Header name: X-KIE-ContentType
Header Value XSTREAM
Hence set proper header
Finally XML should be in the form:
<batch-execution lookup="defaultKieSession">
<insert out-identifier="test">
<test.myproject.HelloWorld>
<message>"Joe"</message>
</test.myproject.HelloWorld>
</insert>
<fire-all-rules/>
<get-objects out-identifier="test"/>
</batch-execution>
Watch out the output "Hello Joe!" in command prompt.
Thanks to Maciej Swiderski for all the support

how to use varnish cache with set-cookie named mp3list and phpsessionid

i am new to php and i am interested to use varnish to improve site performance..
i installed varnish latest version : 4.0.2 varnish
HTTP/1.1 200 OK
Date: Sat, 06 Dec 2014 07:24:47 GMT
Server: Apache/2.2.29 (Unix) mod_ssl/2.2.29 OpenSSL/1.0.1e-fips mod_bwlimited/1.4
X-Powered-By: PHP/5.4.34
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: PHPSESSID=86dc704d405a80d0c012de043cd9408b; path=/
Set-Cookie: Mp3List=WDVMG2G4; expires=Tue, 03-Dec-2024 07:24:47 GMT
Vary: Accept-Encoding
Content-Type: text/html
X-Varnish: 2
Age: 0
Via: 1.1 varnish-v4
Connection: keep-alive
i use cookie named ( mp3list and phpsessionid) so i cant cache my pages,,
i used vcl with
$sub vcl_recv {
call normalize_req_url;
set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");
# Happens before we check if we have this in cache already.
#
# Typically you clean up the request here, removing cookies you don't need,
# rewriting the request, etc.
# If the requested URL starts like "/cart.asp" then immediately pass it to the given
# backend and DO NOT cache the result ("pass" basically means "bypass the cache").
if (req.url ~ "^/playlist\.php$") {
return (pass);
}
}
sub vcl_backend_response {
# Happens after we have read the response headers from the backend.
#
# Here you clean the response headers, removing silly Set-Cookie headers
# and other mistakes your backend does.
if (beresp.http.Set-Cookie)
{
set beresp.http.Set-Cookie = regsub(beresp.http.Set-Cookie, "^php","");
if (beresp.http.Set-Cookie == "")
{
unset beresp.http.Set-Cookie;
}
}
unset beresp.http.Cache-Control;
set beresp.http.Cache-Control = "public";
}
sub vcl_deliver {
# Happens when we have all the pieces we need, and are about to send the
# response to the client.
#
# You can do accounting or modifying the final object here.
# Was a HIT or a MISS?
if ( obj.hits > 0 )
{
set resp.http.X-Cache = "HIT";
}
else
{
set resp.http.X-Cache = "MISS";
}
# And add the number of hits in the header:
set resp.http.X-Cache-Hits = obj.hits;
}
sub normalize_req_url {
# Strip out Google Analytics campaign variables. They are only needed
# by the javascript running on the page
# utm_source, utm_medium, utm_campaign, gclid, ...
if(req.url ~ "(\?|&)(gclid|cx|ie|cof|siteurl|zanpid|origin|utm_[a-z]+|mr:[A-z]+)=") {
set req.url = regsuball(req.url, "(gclid|cx|ie|cof|siteurl|zanpid|origin|utm_[a-z]+|mr:[A- z]+)=[%.-_A-z0-9]+&?", "");
}
set req.url = regsub(req.url, "(\?&?)$", "");
}
sub vcl_hash {
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
hash_data(req.http.Cookie);
}
Now i removed mp3list using this,, still phpsessid is there,, how to remove phpsessid
HTTP/1.1 200 OK
Date: Sat, 06 Dec 2014 07:44:46 GMT
Server: Apache/2.2.29 (Unix) mod_ssl/2.2.29 OpenSSL/1.0.1e-fips mod_bwlimited/1.4
X-Powered-By: PHP/5.4.34
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Pragma: no-cache
Vary: Accept-Encoding
Content-Type: text/html
Set-Cookie: PHPSESSID=ce934af9a97bd7d0fd14304bd49f8fe2; path=/
Cache-Control: public
X-Varnish: 163843
Age: 0
Via: 1.1 varnish-v4
X-Cache: MISS
X-Cache-Hits: 0
Connection: keep-alive
can anyone plz guide me how to bypass phpsessid and edit VCL and use varnish effectively
advance thanks for you ........
As you are noticing PHP sessions and Varnish do not intermix very nicely, ie the phpsessions destroys the cachebility of the urls within varnish as it makes them all unique.
Options:
1. disable php sessions altogether (doubt thats a real option for you).
2. Only enable php sessions on the pages you absolutely need it, so the rest can be cached.
3. split the session-related php logic into separate php files and include those using ESI into your main pages. that will allow you to partially cache the pages (the non-ESI bits).

Directory API: delete member API operation fails for non-Google accounts

We are noticing that group members cannot be removed using the new Directory API if they are not Google accounts. We've tested this with both Dito GAM (note I am the author of GAM) and the Google API Explorer.
The operation functions correctly if the member to be removed is a Google account:
C:\gam-64>gam update group group99#jay.powerposters.org remove google-user#jay.powerposters.org
removing google-user#jay.powerposters.org
connect: (www.googleapis.com, 443)
send: 'DELETE /admin/directory/v1/groups/group99#jay.powerposters.org/members/google-user#jay.powerposters.org?quotaUser=1ee51612c9a0220af0cf5516a990b206e2a619e8&prettyPrint=true
HTTP/1.1
Host: www.googleapis.com
content-length: 0
authorization: Bearer <valid access token>
accept-encoding: gzip, deflate
accept: */*
user-agent: Dito GAM 3.01 / jay#ditoweb.com (Jay Lee) / Python 2.7.5 final / Windows-7-6.1.7601-SP1 AMD64 / google-api-python-client/1.2 (gzip)
reply: 'HTTP/1.1 204 No Content
header: Cache-Control: no-cache, no-store, max-age=0, must-revalidate
header: Pragma: no-cache
header: Expires: Fri, 01 Jan 1990 00:00:00 GMT
header: Date: Wed, 28 Aug 2013 12:57:06 GMT
header: ETag: "cZnI-gy4eI-n1-_cqk7okAteLZk/vyGp6PvFo4RvsFtPoIWeCReyIC8"
header: Server: GSE
this is the expected behavior. However, if the account to be removed is not a Google Account (Google Apps or consumer), an error is returned:
C:\gam-64>gam update group group99#jay.powerposters.org remove not-a-google-account#not-a-google-domain.com
removing not-a-google-account#not-a-google-domain.com
connect: (www.googleapis.com, 443)
send: 'DELETE /admin/directory/v1/groups/group99#jay.powerposters.org/members/not-a-google-account#not-a-google-domain.com?quotaUser=1ee51612c9a0220af0cf5516a990b206e2a619e8&prettyPrint=true
HTTP/1.1
Host: www.googleapis.com\r\ncontent-length: 0
authorization: Bearer <valid oauth 2.0 token>
accept-encoding: gzip, deflate
accept: */*
user-agent: Dito GAM 3.01 / jay#ditoweb.com (Jay Lee) / Python 2.7.5 final / Windows-7-6.1.7601-SP1 AMD64 / google-api-python-client/1.2 (gzip)
reply: 'HTTP/1.1 400 Bad Request
header: Content-Type: application/json; charset=UTF-8
header: Content-Encoding: gzip
header: Date: Wed, 28 Aug 2013 13:00:02 GMT
header: Expires: Wed, 28 Aug 2013 13:00:02 GMT
header: Cache-Control: private, max-age=0
header: X-Content-Type-Options: nosniff
header: X-Frame-Options: SAMEORIGIN
header: X-XSS-Protection: 1; mode=block
header: Content-Length: 122
header: Server: GSE
body: {
"error": {
"errors": [
{
"domain": "global",
"reason": "badRequest",
"message": "Bad Request"
}
],
"code": 400,
"message": "Bad Request"
}
}
Error 400: Bad Request - badRequest
Note that this same behavior shows in the Google API Explorer also:
http://screencast.com/t/fQZCnMYYs
https://developers.google.com/apis-explorer/#s/admin/directory_v1/directory.members.delete?groupKey=group99%2540jay.powerposters.org&memberKey=not-a-google-account%2540not-a-google-domain.com&_h=1&
It should be possible to remove ANY address from a Google Group via the Directory API
I spoke with Google Enterprise support about this and they acknowledged this was a bug (as Silvano has here). After speaking with the dev team, they told me that a work around for this is to delete using the ID of the user instead of email address.
A quick example of how you can delete all members using member ids.
Directory directory =
new Directory.Builder(httpTransport, jsonFactory, credentials)
.setApplicationName(APPLICATION_NAME)
.build();
Members members = directory.members().list(groupKey).execute();
for(Member member : members.getMembers()) {
String memberId = member.getId();
Delete delete = directory.members().delete(groupKey, memberId);
delete.execute();
}
Hope this helps until they can release a fix.
Google has resolved this issue on their end. Removing non-Google email addresses from a Google Group now works without issue.
Here is a working example in PHP with Add and Delete.
function gmail_provision($email, $email_list, $requested_operation) {
// requested_operation should be either add or delete
require_once "google-api-php-client/src/Google_Client.php";
require_once "google-api-php-client/src/contrib/Google_DirectoryService.php";
require_once "google-api-php-client/src/contrib/Google_Oauth2Service.php";
session_start();
$group_scope = 'https://www.googleapis.com/auth/admin.directory.group';
$service_account_email = '.....#developer.gserviceaccount.com';
$service_account_pkcs12_file_path = '/path/to/...-privatekey.p12';
$client_id = '......apps.googleusercontent.com';
$adminEmail = 'admin-account#email.com;
$key = file_get_contents($service_account_pkcs12_file_path);
$auth = new Google_AssertionCredentials($service_account_email, array($group_scope), $key, 'notasecret', 'http://oauth.net/grant_type/jwt/1.0/bearer', $adminEmail);
$client = new Google_Client();
$client->setClientId($client_id); // from API console
$client->setApplicationName("API Project Name");
$client->setUseObjects(true);
$client->setAssertionCredentials($auth);
try {
// Adding or removing from specified list
$sync_status = '';
$service = new Google_DirectoryService($client);
switch ($requested_operation) {
case "add": // Add
$member = new Google_Member(array('email' => $email,
'kind' => 'admin#directory#member',
'role' => 'MEMBER',
'type' => 'USER'));
$service->members->insert($email_list, $member);
break;
case "remove": // Remove
// Users must be removed by ID not by email address
// This is slow and should be changed when
// Google fixes the bug and allows delete by email.
// Get a list of email addresses in the list
$results = $service->members->listMembers($email_list);
// Push results into an array
$arrResults = get_object_vars($results);
// Recursive iterator
$arrIt = new RecursiveIteratorIterator(new RecursiveArrayIterator($arrResults));
// Cycle through the array searching for the email address
foreach ($arrIt as $sub) {
$subArray = $arrIt->getSubIterator();
if ($subArray['email'] === $email) {
// Store an array of the details associated
// with the email address
$outputArray[] = iterator_to_array($subArray);
}
}
// Delete by id
$service->members->delete($email_list, $outputArray[0]['id']);
break;
}
}
catch (Google_ServiceException $e) {
// $e->getCode()
// $e->getMessage()
}
catch (Google_Exception $e) {
// $e->getCode()
// $e->getMessage()
}
catch (Zend_Gdata_Gapps_ServiceException $e) {
// $e->getCode()
// $e->getMessage()
}
}