Search Unity

Online image texture load fails in WebGL: No 'Access-Control-Allow-Origin' header.....

Discussion in 'Web' started by ki_ha1984, Feb 12, 2016.

  1. ki_ha1984

    ki_ha1984

    Joined:
    Aug 24, 2014
    Posts:
    111
    Hi,

    I have an application which load image from URL as texture in an object, with the following code:

    Code (CSharp):
    1.  
    2.  
    3. StartCoroutine (SetTheTexture (theObject,texture));  
    4.  
    5. IEnumerator SetTheTexture (GameObject objt, string imageUrl) {
    6.         WWW www = new WWW (imageUrl);
    7.         yield return www;  
    8.         objt.GetComponent<Renderer>().material.mainTexture = www.texture;
    9.         }
    10.  
    Until now it worked very well with unity webplayer applications, now i try to export it as WebGL and i get the following error

    Code (CSharp):
    1.  
    2. MLHttpRequest cannot load http://www.randomdomain.gr/dyncontent/repository/repository/37667777/thumbnail.jpg. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://mydomain.gr' is therefore not allowed access.
    3.  
    After searching the unity forum and internet i find out the Unity WebGL Networking and several other threads that tries to explain how to use the following 4 commands

    Code (CSharp):
    1. "Access-Control-Allow-Credentials": "true",
    2. "Access-Control-Allow-Headers": "Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time",
    3. "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
    4. "Access-Control-Allow-Origin": "*",
    I try to insert them into the .htaccess file inside of the Release folder(Release/.htaccess) as follow:
    Code (CSharp):
    1.  
    2. Header set Access-Control-Allow-Credentials: "true"
    3. Header set Access-Control-Allow-Headers: "Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time"
    4. Header set Access-Control-Allow-Methods: "GET, POST, OPTIONS"
    5. Header set Access-Control-Allow-Origin: "*"
    6.  
    7. Options +FollowSymLinks
    8. RewriteEngine on
    9.  
    10. RewriteCond %{HTTP:Accept-encoding} gzip
    11. RewriteCond %{REQUEST_FILENAME}gz -f
    12. RewriteRule ^(.*)\.js$ $1\.jsgz [L]
    13.  
    14. RewriteCond %{HTTP:Accept-encoding} gzip
    15. RewriteCond %{REQUEST_FILENAME}gz -f
    16. RewriteRule ^(.*)\.data$ $1\.datagz [L]
    17.  
    18. RewriteCond %{HTTP:Accept-encoding} gzip
    19. RewriteCond %{REQUEST_FILENAME}gz -f
    20. RewriteRule ^(.*)\.mem$ $1\.memgz [L]
    21.  
    22. RewriteCond %{HTTP:Accept-encoding} gzip
    23. RewriteCond %{REQUEST_FILENAME}gz -f
    24. RewriteRule ^(.*)\.unity3d$ $1\.unity3dgz [L]
    25.  
    26. AddEncoding gzip .jsgz
    27. AddEncoding gzip .datagz
    28. AddEncoding gzip .memgz
    29. AddEncoding gzip .unity3dgz
    30.  

    i have iis8 and linux server.

    Were and how i have to put these 4 command, in order to load the image from external urls ?


    Thank you in advanced
     
  2. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello ki_ha1984.

    The CORS headers should be added to the response of the image hosting server, not the WebGL hosting server.
    If you do not have access to the image hosting server configuration (i.e. it is not your server but some public one, or you want to be able to load an image from an arbitrary url), cross-domain WWW request can still be performed through an intermediate proxy server, which can append necessary headers to the proxied response.
     
    Last edited: Feb 12, 2016
  3. ki_ha1984

    ki_ha1984

    Joined:
    Aug 24, 2014
    Posts:
    111
    Hi alexsuvorov and hank you for your answer,

    Yes, the images are from several different servers, because use the europena api to call images and the api returns images from diverse servers. this means that i cannot access all those servers to enable the CORS.

    Can you please explain me how can i make this www request through proxy server ?

    Do you have any example ?

    Thank you again
     
  4. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    You may use any php or node.js proxy available on the web. However if you want to start from something simple, I have just made a small php proxy for you (assuming that your server supports php). Upload the following proxy.php to the same server where your WebGL content is hosted, let's say to your-webgl-domain/some-folder/proxy.php
    Code (php):
    1.  
    2. <?php
    3. $url = $_GET["url"];
    4. if (!preg_match("~^https?://~i", $url)) {
    5.   $url = "http://" . $url;
    6. }
    7.  
    8. $request_headers = array();
    9. foreach (getallheaders() as $header => $value) {
    10.   $request_headers[] = $header . ": " . $value;
    11. }
    12.  
    13. $ch = curl_init($url);
    14. curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers);
    15. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    16. curl_setopt($ch, CURLOPT_HEADER, true);
    17. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    18. $response = curl_exec($ch);
    19. $response_headers = explode("\r\n", substr($response, 0, curl_getinfo($ch, CURLINFO_HEADER_SIZE)));
    20. $response_body = substr($response, curl_getinfo($ch, CURLINFO_HEADER_SIZE));
    21. curl_close($ch);
    22.  
    23. $propagate_headers = array("content-type"); //add additonal headers you want to propagate to this array in lower case
    24. foreach ($response_headers as $header) {
    25.   if (in_array(strtolower(explode(": ", $header)[0]), $propagate_headers)) {
    26.     header($header);
    27.   }
    28. }
    29.  
    30. // you only need the following headers if the proxy and WebGL are hosted on different domains
    31. header("Access-Control-Allow-Credentials: true");
    32. header("Access-Control-Allow-Headers: Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time");
    33. header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
    34. header("Access-Control-Allow-Origin: *");
    35. // you can replace the "*" above with "http://your-webgl-domain" to prevent unauthorized use of your proxy
    36. // in which case you should also append "Origin: http://your-webgl-domain" header to the request
    37.  
    38. echo $response_body;
    Note: when copying php code from the snippet above make sure that your php file starts immediately with <?php without any preceding lines, spaces or character encoding markers (otherwise proxy will not work properly).

    Now add the following prefix to your url:
    Code (CSharp):
    1. IEnumerator SetTheTexture (GameObject objt, string imageUrl) {
    2.   WWW www = new WWW ("http://your-webgl-domain/some-folder/proxy.php?url=" + imageUrl);
    3.   ...
    Your cross-domain image request should already work now, however some security precautions might be considered. The basic rule is to use "Access-Control-Allow-Origin: *" wisely, especially for a proxy server, as it has serious security implications.

    If your proxy is hosted on the same domain as your WebGL content (as described in the example above), then you should comment out the last 4 CORS headers in the php (in this case WWW request from the browser will access the same origin and therefore CORS headers are not required anyway). Otherwise one might be able to perform cross-domain requests in another direction (from an external website to other parts of your website through your proxy) which should clearly be avoided.

    If for some reason you decide to host your proxy on a different domain, then specifying "Access-Control-Allow-Origin: *" will let anybody use your proxy for performing arbitrary cross-domain requests. If you want to avoid this, you can limit the use of the proxy to your WebGL domain requests by specifying
    Code (php):
    1. header("Access-Control-Allow-Origin: http://your-webgl-domain");
    in the proxy.php, and providing the matching Origin header in the WWW request, i.e:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. ...
    3. Dictionary<string, string> headers = new Dictionary<string, string>();
    4. headers.Add("Origin", "http://your-webgl-domain");
    5. WWW www = new WWW ("http://your-proxy-domain/some-folder/proxy.php?url=" + imageUrl, null, headers);
    I hope this helps. Let me know if you need any adjustments to make it work for your specific use case.
     
    Last edited: Feb 15, 2016
    msrafiyenko likes this.
  5. msrafiyenko

    msrafiyenko

    Joined:
    Sep 25, 2014
    Posts:
    5
    Hi Alex!

    I have the same problems as described in previous section.
    I'd try to connect higscores backend to my webGL project and recieved this message "No 'Access-Control-Allow-Origin' header.....".
    After all I found your article about proxy.php. I've done everything as you say (create proxy.php, add proxy url + "?url=" to my request) and it works fine in editor.

    But, when I upload my project on server I recieved this message in the consol of my browser: "FormatException: Input string was not in the correct format"...


    I'll be so thankful if you have some solutions about what does this error related to.
    And, the main question is: How do you think, why the same string (server responce in this case) is suitable for editor code, but in the same time inappropriate for compiled webGL code?

    Thank you for reading this :)
     
  6. ki_ha1984

    ki_ha1984

    Joined:
    Aug 24, 2014
    Posts:
    111
    Hi alexsuvorov,

    It works perfectly, but in case someone uses IIS and curl does not work in php use the file_get_content() function as follow:

    Code (CSharp):
    1.  
    2. $context = stream_context_create($request_headers);
    3. $response = file_get_contents($_GET["url"], false, $context);
    4.  
    Thanks again Alex,
     
    msrafiyenko likes this.
  7. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello msrafiyenko.

    The "FormatException: Input string was not in the correct format" seems to be a c# exception, so we should first determine what exactly is causing it. Does the exception occur right after the actual request? What would the WWW.bytes return? (if you need help with this you can share your server url using private message)
     
  8. msrafiyenko

    msrafiyenko

    Joined:
    Sep 25, 2014
    Posts:
    5
    As ki_ha1984 says IIS and curl does not work in php.

    After I change this lines of code:

    1. $ch = curl_init($_GET["url"]);
    2. curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers);
    3. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    4. curl_setopt($ch, CURLOPT_HEADER, true);
    5. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    6. $response = curl_exec($ch);
    to this:
    1. $context = stream_context_create($request_headers);
    2. $response = file_get_contents($_GET["url"], false, $context);
    Everything starts working properly.

    By the way, this lines is still throws an Error 500 in browser if you have php version under 5.6:

    if (in_array(strtolower(explode(":", $header)[0]), $propagate_headers)) {
    header($header);
    }

    So, after I've fixed all this stuff everything goes perfectly well.
    Thank you all for help!
     
  9. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Please pay attention to the following security warning:
    curl_exec and file_get_contents can be used to access server file system and resources in the local network of the hosting server. When using curl_exec or file_get_contents you should first validate that the requested resource is an internet address (i.e. starts with http:// or https://) and that the address is external to the hosting server network (i.e. not localhost, 127.0.0.1 or other ip from the internal server network etc.). Otherwise one may use something like
    proxy.php?url=../../some-server-file for file_get_contents or
    proxy.php?url=file:///c:/inetpub/wwwroot/some-server-file for curl_exec
    to access your local server files.
    Forcing http:// prefix for urls which do not specify supported protocol should do the trick, for example:
    Code (php):
    1.  
    2. <?php
    3. $url = $_GET["url"];
    4. if (!preg_match("~^https?://~i", $url)) {
    5.   $url = "http://" . $url;
    6. }
    7. ...
    8. $ch = curl_init($url);
    9.  
    Also, the php proxy displayed above is just a light version, something that you can use for initial testing. There are a lot of open source proxies on the web, which also support POST, PUT, OPTIONS methods etc, properly handle redirections and other corner cases.
     
    Last edited: Feb 15, 2016
  10. commodore

    commodore

    Joined:
    May 30, 2012
    Posts:
    40
     
  11. commodore

    commodore

    Joined:
    May 30, 2012
    Posts:
    40
    @alexsuvorov What's wrong with doing something like this?

    Code (CSharp):
    1. <?php
    2.     $url = $_GET["url"];
    3.  
    4.     header('Content-type: image/jpeg');
    5.     echo file_get_contents($url);
    6. ?>
     
  12. omar_5

    omar_5

    Joined:
    Feb 8, 2020
    Posts:
    9
    the proxy solution didn't work out for me,
    but I've found a "somehow" solution following this guy
    Here