Меню

Ошибка cors javascript fetch

I’m building a front-end only basic Weather App using reactjs. For API requests I’m using Fetch API.
In my app, I’m getting the current location from a simple API I found
and it gives the location as a JSON object. But when I request it through Fetch API, I’m getting this error.

Failed to load http://ip-api.com/json: Request header field Access-Control-Allow-Origin is not allowed by Access-Control-Allow-Headers in preflight response.

So I searched through and found multiple solutions to fix this.

  1. Enabling CORS in Chrome solves the error but when I deploy the app on heroku, how can I access it through a mobile device without running into the same CORS issue.
  2. I found an proxy API which enables the CORS requests. But as this is a location request, this gives me the location of the proxy server. So it’s not a solution.
  3. I’ve gone through this Stackoverflow question and added the headers to the header in my http request but it doesn’t solve the problem. (Still it gives the same error).

So how can I solve the issue permanently ? What’s the best solution I can use to solve the CORS issue for http requests in Fetch API ?

asked Feb 11, 2018 at 4:09

Thidasa Pankaja's user avatar

Thidasa PankajaThidasa Pankaja

8336 gold badges25 silver badges40 bronze badges

1

if you are making a post, put or patch request, you have to stringify your data with body: JSON.stringify(data)

fetch(URL, 
        {
            method: "POST", 
            body: JSON.stringify(data),
            mode: 'cors',
            headers: {
                'Content-Type': 'application/json',
            }
        }
    ).then(response => response.json())
    .then(data => {
        ....
    })
    .catch((err) => {
        ....
        })
    });

answered Apr 9, 2021 at 1:45

Nafiu Lawal's user avatar

1

That API appears to be permissive, responding with Access-Control-Allow-Origin:*

I haven’t figured out what is causing your problem, but I don’t think it is simply the fetch API.

This worked fine for me in both Firefox and Chrome…

fetch('http://ip-api.com/json')
   .then( response => response.json() )
   .then( data => console.log(data) )

answered Feb 11, 2018 at 5:15

Brent Bradburn's user avatar

Brent BradburnBrent Bradburn

49.6k17 gold badges146 silver badges169 bronze badges

4

You should use the proxy solution, but pass it the IP of the client instead of the proxy. Here is an example URL format for the API you specified, using the IP of WikiMedia:

http://ip-api.com/json/208.80.152.201

answered Feb 11, 2018 at 4:16

Tallboy's user avatar

TallboyTallboy

12.6k12 gold badges77 silver badges171 bronze badges

4

Many websites have JavaScript functions that make network requests to a server, such as a REST API. The web pages and APIs are often in different domains. This introduces security issues in that any website can request data from an API. Cross-Origin Resource Sharing (CORS) provides a solution to these issues. It became a W3C recommendation in 2014. It makes it the responsibility of the web browser to prevent unauthorized access to APIs. All modern web browsers enforce CORS. They prevent JavaScript from obtaining data from a server in a domain different than the domain the website was loaded from, unless the REST API server gives permission.

From a developer’s perspective, CORS is often a cause of much grief when it blocks network requests. CORS provides a number of different mechanisms for limiting JavaScript access to APIs. It is often not obvious which mechanism is blocking the request. We are going to build a simple web application that makes REST calls to a server in a different domain. We will deliberately make requests that the browser will block because of CORS policies and then show how to fix the issues. Let’s get started!

NOTE: The code for this project can be found on GitHub.

Table of Contents

  • Prerequisites to Building a Go Application
  • How to Build a Simple Web Front End
  • How to Build a Simple REST API in Go
  • How to Solve a Simple CORS Issue
  • Allowing Access from Any Origin Domain
  • CORS in Flight
  • What Else Does CORS Block?
  • Restrictions on Response Headers
  • Credentials Are a Special Case
  • Control CORS Cache Configuration
  • How to Prevent CORS Issues with Okta
  • How CORS Prevents Security Issues

Prerequisites to Building a Go Application

First things first, if you don’t already have Go installed on your computer you will need to download and install the Go Programming Language.

Now, create a directory where all of our future code will live.

Finally, we will make our directory a Go module and install the Gin package (a Go web framework) to implement a web server.

go mod init cors
go get github.com/gin-gonic/gin
go get github.com/gin-contrib/static

A file called go.mod will get created containing the dependencies.

How to Build a Simple Web Front End

We are going to build a simple HTML and JavaScript front end and serve it from a web server written using Gin.

First of all, create a directory called frontend and create a file called frontend/index.html with the following content:

<html>
    <head>
        <meta charset="UTF-8" />
        <title>Fixing Common Issues with CORS</title>
    </head>
    <body>
        <h1>Fixing Common Issues with CORS</h1>
        <div>
            <textarea id="messages" name="messages" rows="10" cols="50">Messages</textarea><br/>
            <form id="form1">
                <input type="button" value="Get v1" onclick="onGet('v1')"/>
                <input type="button" value="Get v2" onclick="onGet('v2')"/>
            </form>
        </div>
    </body>
</html>

The web page has a text area to display messages and a simple form with two buttons. When a button is clicked it calls the JavaScript function onGet() passing it a version number. The idea being that v1 requests will always fail due to CORS issues, and v2 will fix the issue.

Next, create a file called frontend/control.js containing the following JavaScript:

function onGet(version) {
    const url = "http://localhost:8000/api/" + version + "/messages";
    var headers = {}
    
    fetch(url, {
        method : "GET",
        mode: 'cors',
        headers: headers
    })
    .then((response) => {
        if (!response.ok) {
            throw new Error(response.error)
        }
        return response.json();
    })
    .then(data => {
        document.getElementById('messages').value = data.messages;
    })
    .catch(function(error) {
        document.getElementById('messages').value = error;
    });
}

The onGet() function inserts the version number into the URL and then makes a fetch request to the API server. A successful request will return a list of messages. The messages are displayed in the text area.

Finally, create a file called frontend/.go containing the following Go code:

package main

import (
	"github.com/gin-contrib/static"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.Use(static.Serve("/", static.LocalFile("./frontend", false)))
	r.Run(":8080")
}

This code simply serves the contents of the frontend directory on requests on port 8080. Note that JavaScript makes a call to port http://localhost:8000 which is a separate service.

Start the server and point a web browser at http://localhost:8080 to see the static content.

How to Build a Simple REST API in Go

Create a directory called rest to contain the code for a basic REST API.

NOTE: A separate directory is required as Go doesn’t allow two program entry points in the same directory.

Create a file called rest/server.go containing the following Go code:

package main

import (
	"fmt"
	"strconv"
	"net/http"

	"github.com/gin-gonic/gin"
)

var messages []string

func GetMessages(c *gin.Context) {
	version := c.Param("version")
	fmt.Println("Version", version)
	c.JSON(http.StatusOK, gin.H{"messages": messages})
}

func main() {
	messages = append(messages, "Hello CORS!")
	r := gin.Default()
	r.GET("/api/:version/messages", GetMessages)
	r.Run(":8000")
}

A list called messages is created to hold message objects.

The function GetMessages() is called whenever a GET request is made to the specified URL. It returns a JSON string containing the messages. The URL contains a path parameter which will be v1 or v2. The server listens on port 8000.

The server can be run using:

How to Solve a Simple CORS Issue

We now have two servers—the front-end one on http://localhost:8080, and the REST API server on http://localhost:8000. Even though the two servers have the same hostname, the fact that they are listening on different port numbers puts them in different domains from the CORS perspective. The domain of the web content is referred to as the origin. If the JavaScript fetch request specifies cors a request header will be added identifying the origin.

Origin: http://localhost:8080

Make sure both the frontend and REST servers are running.

Next, point a web browser at http://localhost:8080 to display the web page. We are going to get JavaScript errors, so open your browser’s developer console so that we can see what is going on. In Chrome it is *View** > Developer > Developer Tools.

Next, click on the Send v1 button. You will get a JavaScript error displayed in the console:

Access to fetch at ‘http://localhost:8000/api/v1/messages’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

The message says that the browser has blocked the request because of a CORS policy. It suggests two solutions. The second suggestion is to change the mode from cors to no-cors in the JavaScript fetch request. This is not an option as the browser always deletes the response data when in no-cors mode to prevent data from being read by an unauthorized client.

The solution to the issue is for the server to set a response header that allows the browser to make cross-domain requests to it.

Access-Control-Allow-Origin: http://localhost:8080

This tells the web browser that the cross-origin requests are to be allowed for the specified domain. If the domain specified in the response header matches the domain of the web page, specified in the Origin request header, then the browser will not block the response being received by JavaScript.

We are going to set the header when the URL contains v2. Change the GetMessages() function in cors/server.go to the following:

func GetMessages(c *gin.Context) {
	version := c.Param("version")
	fmt.Println("Version", version)
	if version == "v2" {
		c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
	}
	c.JSON(http.StatusOK, gin.H{"messages": messages})
}

This sets a header to allow cross-origin requests for the v2 URI.

Restart the server and go to the web page. If you click on Get v1 you will get blocked by CORS. If you click on Get v2, the request will be allowed.

A response can only have at most one Access-Control-Allow-Origin header. The header can only specify only one domain. If the server needs to allow requests from multiple origin domains, it needs to generate an Access-Control-Allow-Origin response header with the same value as the Origin request header.

Allowing Access from Any Origin Domain

There is an option to prevent CORS from blocking any domain. This is very popular with developers!

Access-Control-Allow-Origin: *

Be careful when using this option. It will get flagged in a security audit. It may also be in violation of an information security policy, which could have serious consequences!

CORS in Flight

Although we have fixed the main CORS issue, there are some limitations. One of the limitations is that only the HTTP GET, and OPTIONS methods are allowed. The GET and OPTIONS methods are read-only and are considered safe as they don’t modify existing content. The POST, PUT, and DELETE methods can add or change existing content. These are considered unsafe. Let’s see what happens when we make a PUT request.

First of all, add a new form to client/index.html:

<form id="form2">
    <input type="text" id="puttext" name="puttext"/>
    <input type="button" value="Put v1" onclick="onPut('v1')"/>
    <input type="button" value="Put v2" onclick="onPut('v2')"/>
</form>

This form has a text input and the two send buttons as before that call a new JavaScript function.

Next, add the JavaScript funtion to client/control.js:

function onPut(version) {
    const url = "http://localhost:8000/api/" + version + "/messages/0";
    var headers = {}

    fetch(url, {
        method : "PUT",
        mode: 'cors',
        headers: headers,
        body: new URLSearchParams(new FormData(document.getElementById("form2"))),
    })
    .then((response) => {
        if (!response.ok) {
            throw new Error(response.error)
        }
        return response.json();
    })
    .then(data => {
        document.getElementById('messages').value = data.messages;
    })
    .catch(function(error) {
        document.getElementById('messages').value = error;
    });
}

This makes a PUT request sending the form parameters in the request body. Note that the URI ends in /0. This means that the request is to create or change the message with the identifier 0.

Next, define a PUT handler in the main() function of rest/server.go:

r.PUT("/api/:version/messages/:id", PutMessage)

The message identifier is extracted as a path parameter.

Finally, add the request handler function to rest/server.go:

func PutMessage(c *gin.Context) {
	version := c.Param("version")
	id, _ := strconv.Atoi(c.Param("id"))
	text := c.PostForm("puttext")
	messages[id] = text
	if version == "v2" {
	    c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
	}
	c.JSON(http.StatusOK, gin.H{"messages": messages})
}

This updates the message from the form data and sends the new list of messages. The function also always sets the allow origin header, as we know that it is required.

Now, restart the servers and load the web page. Make sure that the developer console is open. Add some text to the text input and hit the Send v1 button.

You will see a slightly different CORS error in the console:

Access to fetch at ‘http://localhost:8000/api/v1/messages/0’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

This is saying that a preflight check was made, and that it didn’t set the Access-Control-Allow-Origin header.

Now, look at the console output from the API server:

[GIN] 2020/12/01 - 11:10:09 | 404 |  447ns |  ::1 | OPTIONS  "/api/v1/messages/0"

So, what is happening here? JavaScript is trying to make a PUT request. This is not allowed by CORS policy. In the GET example, the browser made the request and blocked the response. In this case, the browser refuses to make the PUT request. Instead, it sent an OPTIONS request to the same URI. It will only send the PUT if the OPTIONS request returns the correct CORS header. This is called a preflight request. As the server doesn’t know what method the OPTIONS preflight request is for, it specifies the method in a request header:

Access-Control-Request-Method: PUT

Let’s fix this by adding a handler for the OPTIONS request that sets the allow origin header in cors/server.go:

func OptionMessage(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
}

func main() {
	messages = append(messages, "Hello CORS!")
	r := gin.Default()
	r.GET("/api/:version/messages", GetMessages)
	r.PUT("/api/:version/messages/:id", PutMessage)
	r.OPTIONS("/api/v2/messages/:id", OptionMessage)
	r.Run(":8000")
}

Notice that the OPTIONS handler is only set for the v2 URI as we don’t want to fix v1.

Now, restart the server and send a message using the Put v2 button. We get yet another CORS error!

Access to fetch at ‘http://localhost:8000/api/v2/messages/0’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.

This is saying that the preflight check needs another header to stop the PUT request from being blocked.

Add the response header to cors/server.go:

func OptionMessage(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
	c.Header("Access-Control-Allow-Methods", "GET, OPTIONS, POST, PUT")
}

Restart the server and resend the message. The CORS issues are resolved.

What Else Does CORS Block?

CORS has a very restrictive policy regarding which HTTP request headers are allowed. It only allows safe listed request headers. These are Accept, Accept-Language, Content-Language, and Content-Type. They can only contain printable characters and some punctuation characters are not allowed. Header values can’t have more than 128 characters.

There are further restrictions on the Content-Type header. It can only be one of application/x-www-form-urlencoded, multipart/form-data, and text/plain. It is interesting to note that application/json is not allowed.

Let’s see what happens if we send a custom request header. Modify the onPut() function in frontend/control.jsto set a header:

var headers = { "X-Token": "abcd1234" }

Now, make sure that the servers are running and load or reload the web page. Type in a message and click the Put v1 button. You will see a CORS error in the developer console.

Access to fetch at ‘http://localhost:8000/api/v2/messages/0’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Request header field x-token is not allowed by Access-Control-Allow-Headers in preflight response.

Any header which is not CORS safe-listed causes a preflight request. This also contains a request header that specifies the header that needs to be allowed:

Access-Control-Request-Headers: x-token

Note that the header name x-token is specified in lowercase.

The preflight response can allow non-safe-listed headers and can remove restrictions on safe listed headers:

Access-Control-Allow-Headers: X_Token, Content-Type

Next, change the function in cors/server.go to allow the custom header:

func OptionMessage(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
	c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT")
	c.Header("Access-Control-Allow-Headers", "X-Token")
}

Restart the server and resend the message by clicking the Put v2 button. The request should now be successful.

CORS also places restrictions on response headers. There are seven whitelisted response headers: Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified, and Pragma. These are the only response headers that can be accessed from JavaScript. Let’s see this in action.

First of all, add a text area to frontend/index.html to display the headers:

<textarea id="headers" name="headers" rows="10" cols="50">Headers</textarea><br/>

Next, replace the first then block in the onPut function in frontend/control.js to display the response headers:

.then((response) => {
    if (!response.ok) {
        throw new Error(response.error)
    }
    response.headers.forEach(function(val, key) {
        document.getElementById('headers').value += 'n' + key + ': ' + val; 
    });
    return response.json();
})

Finally, set a custom header in rest/server.go:

func PutMessage(c *gin.Context) {
	version := c.Param("version")
	id, _ := strconv.Atoi(c.Param("id"))
	text := c.PostForm("puttext")
	messages[id] = text
	if version == "v2" {
	    c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
	}
	c.Header("X-Custom", "123456789")
	c.JSON(http.StatusOK, gin.H{"messages": messages})
}

Now, restart the server and reload the web page. Type in a message and hit Put v2. You should see some headers displayed, but not the custom header. CORS has blocked it.

This can be fixed by setting another response header to expose the custom header in server/server.go:

func PutMessage(c *gin.Context) {
	version := c.Param("version")
	id, _ := strconv.Atoi(c.Param("id"))
	text := c.PostForm("puttext")
	messages[id] = text
	if version == "v2" {
        c.Header("Access-Control-Expose-Headers", "X-Custom")
	    c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
	}
	c.Header("X-Custom", "123456789")
	c.JSON(http.StatusOK, gin.H{"messages": messages})
}

Restart the server and reload the web page. Type in a message and hit Put v2. You should see some headers displayed, including the custom header. CORS has now allowed it.

Credentials Are a Special Case

There is yet another CORS blocking scenario. JavaScript has a credentials request mode. This determines how user credentials, such as cookies are handled. The options are:

  • omit: Never send or receive cookies.
  • same-origin: This is the default, that allows user credentials to be sent to the same origin.
  • include: Send user credentials even if cross-origin.

Let’s see what happens.

Modify the fetch call in the onPut() function in frontend/Control.js:

fetch(url, {
    method : "PUT",
    mode: 'cors',
    credentials: 'include',
    headers: headers,
    body: new URLSearchParams(new FormData(document.getElementById("form2"))),
})

Now, make sure that the client and server are running and reload the web page. Send a message as before. You will get another CORS error in the developer console:

Access to fetch at ‘http://localhost:8000/api/v2/messages/0’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: The value of the ‘Access-Control-Allow-Credentials’ header in the response is ‘’ which must be ‘true’ when the request’s credentials mode is ‘include’.

The fix for this is to add another header to both the OptionMessage(), and PutMessage() functions in cors/server.go as the header needs to be present in both the preflight request and the actual request:

c.Header("Access-Control-Allow-Credentials", "true")

The request should now work correctly.

The credentials issue can also be resolved on the client by setting the credentials mode to omit and sending credentials as request parameters, instead of using cookies.

Control CORS Cache Configuration

The is one more CORS header that hasn’t yet been discussed. The browser can cache the preflight request results. The Access-Control-Max-Age header specifies the maximum time, in seconds, the results can be cached. It should be added to the preflight response headers as it controls how long the Access-Control-Allow-Methods and Access-Control-Allow-Headers results can be cached.

Access-Control-Max-Age: 600

Browsers typically have a cap on the maximum time. Setting the time to -1 prevents caching and forces a preflight check on all calls that require it.

How to Prevent CORS Issues with Okta

Authentication providers, such as Okta, need to handle cross-domain requests to their authentication servers and API servers. This is done by providing a list of trusted origins. See Okta Enable CORS for more information.

How CORS Prevents Security Issues

CORS is an important security feature that is designed to prevent JavaScript clients from accessing data from other domains without authorization.

Modern web browsers implement CORS to block cross-domain JavaScript requests by default. The server needs to authorize cross-domain requests by setting the correct response headers.

CORS has several layers. Simply allowing an origin domain only works for a subset of request methods and request headers. Some request methods and headers force a preflight request as a further means of protecting data. The actual request is only allowed if the preflight response headers permit it.

CORS is a major pain point for developers. If a request is blocked, it is not easy to understand why, and how to fix it. An understanding of the nature of the blocked request, and of the CORS mechanisms, can easily overcome such issues.

If you enjoyed this post, you might like related ones on this blog:

  • What is the OAuth 2.0 Implicit Grant Type?
  • Combat Side-Channel Attacks with Cross-Origin Read Blocking

Follow us for more great content and updates from our team! You can find us on Twitter, Facebook, subscribe to our YouTube Channel or start the conversation below.

cigwe416

What is CORS?

cors

Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any other origins (domain, scheme, or port) than its own from which a browser should permit loading of resources — MDN

This definition might seem confusing so let me try to explain it in simpler terms.

CORS is a browser policy that allows you to make requests from one website to another which by default is not allowed by another browser policy called Same Origin Policy.

This is an error that is mostly addressed from the backend of an API.
The problem here is when you are trying to call a public API without the CORS error being fixed and you can’t reach the developers that developed the API.

In this tutorial, I’ll be showing you how to by-pass CORS errors using Vanilla Javascript when you are in such a situation.

The API we are going to be using is a Quote Generator API.

http://api.forismatic.com/api/1.0/

In other to get list of Quotes, we need to append this to the base URL

?method=getQuote&lang=en&format=json.

So the full URL becomes;

http://api.forismatic.com/api/1.0/?method=getQuote&lang=en&format=json

In other to make the API call, we need to create a Javascript file and call the endpoint.We would be using the fetch API.

This would look like this;

// Get quote from API
async function getQuote() {
  const apiUrl = 'http://api.forismatic.com/api/1.0/?method=getQuote&lang=en&format=json';
  try {
     const response = await fetch(apiUrl);
     const data = await response.json();
     console.log({data});
  } catch (error) {
     console.log(error);
      }
  }
// On load
getQuote();

Enter fullscreen mode

Exit fullscreen mode

If you run this code in your browser and open up your console, you should see the error below;

Screen Shot 2021-03-04 at 6.39.46 PM

In other to fix this error, add the following lines of code;

// Get quote from API
async function getQuote() {
  const proxyUrl = 'https://cors-anywhere.herokuapp.com/' -> this line;
  const apiUrl = 'http://api.forismatic.com/api/1.0/?method=getQuote&lang=en&format=json';
try {

     const response = await fetch(proxyUrl + apiUrl) -> this line;
     const data = await response.json();
     console.log({data});
  } catch (error) {
     console.log(error);
      }
  }
// On load
getQuote();

Enter fullscreen mode

Exit fullscreen mode

This URL https://cors-anywhere.herokuapp.com/ is also a public API that was created by someone to fix the CORS error.

N.B: You might still get some errors on your console even after following the steps I just showed.If this happens, go to this URL

   `https://cors-anywhere.herokuapp.com/corsdemo`

Enter fullscreen mode

Exit fullscreen mode

and follow the instructions there.

Thanks for taking your time to read this Article. Your feedback and comment is highly welcomed.

Fetch API предоставляет интерфейс JavaScript для работы с запросами и ответами HTTP. Он также предоставляет глобальный метод fetch() (en-US), который позволяет легко и логично получать ресурсы по сети асинхронно.

Подобная функциональность ранее достигалась с помощью XMLHttpRequest. Fetch представляет собой лучшую альтернативу, которая может быть легко использована другими технологиями, такими как Service Workers (en-US). Fetch также обеспечивает единое логическое место для определения других связанных с HTTP понятий, такие как CORS и расширения для HTTP.

Обратите внимание, fetch спецификация отличается от jQuery.ajax() в основном в двух пунктах:

  • Promise возвращаемый вызовом fetch() не перейдёт в состояние «отклонено» из-за ответа HTTP, который считается ошибкой, даже если ответ HTTP 404 или 500. Вместо этого, он будет выполнен нормально (с значением false в статусе ok ) и будет отклонён только при сбое сети или если что-то помешало запросу выполниться.
  • По умолчанию, fetch не будет отправлять или получать cookie файлы с сервера, в результате чего запросы будут осуществляться без проверки подлинности, что приведёт к неаутентифицированным запросам, если сайт полагается на проверку пользовательской сессии (для отправки cookie файлов в аргументе init options должно быть задано значение свойства credentials отличное от значения по умолчанию omit).

Примечание: 25 августа 2017 г. в спецификации изменилось значение по умолчанию свойства credentials на same-origin. Firefox применяет это изменение с версии 61.0b13.

Базовый запрос на получение данных действительно прост в настройке. Взгляните на следующий код:

fetch('http://example.com/movies.json')
  .then((response) => {
    return response.json();
  })
  .then((data) => {
    console.log(data);
  });

Здесь мы забираем JSON файл по сети и выводим его содержимое в консоль. Самый простой способ использования fetch() заключается в вызове этой функции с одним аргументом — строкой, содержащей путь к ресурсу, который вы хотите получить — которая возвращает promise, содержащее ответ (объект Response).

Конечно, это просто HTTP-ответ, а не фактический JSON. Чтобы извлечь содержимое тела JSON из ответа, мы используем json() (en-US) метод (определён миксином Body, который реализован в объектах Request и Response.)

Примечание: Миксин Body имеет подобные методы для извлечения других типов контента; см. раздел Тело.

Fetch-запросы контролируются посредством директивы connect-src (Content Security Policy (en-US)), а не директивой извлекаемых ресурсов.

Установка параметров запроса

Метод fetch() может принимать второй параметр — объект init, который позволяет вам контролировать различные настройки:

// Пример отправки POST запроса:
async function postData(url = '', data = {}) {
  // Default options are marked with *
  const response = await fetch(url, {
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, *cors, same-origin
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, *same-origin, omit
    headers: {
      'Content-Type': 'application/json'
      // 'Content-Type': 'application/x-www-form-urlencoded',
    },
    redirect: 'follow', // manual, *follow, error
    referrerPolicy: 'no-referrer', // no-referrer, *client
    body: JSON.stringify(data) // body data type must match "Content-Type" header
  });
  return await response.json(); // parses JSON response into native JavaScript objects
}

postData('https://example.com/answer', { answer: 42 })
  .then((data) => {
    console.log(data); // JSON data parsed by `response.json()` call
  });

С подробным описанием функции и полным списком параметров вы можете ознакомиться на странице fetch() (en-US).

Отправка запроса с учётными данными

Чтобы браузеры могли отправлять запрос с учётными данными (даже для cross-origin запросов), добавьте credentials: 'include' в объект init, передаваемый вами в метод fetch():

fetch('https://example.com', {
  credentials: 'include'
})

Если вы хотите отправлять запрос с учётными данными только если URL принадлежит одному источнику (origin) что и вызывающий его скрипт, добавьте credentials: ‘same-origin’.

// Вызывающий скрипт принадлежит источнику 'https://example.com'

fetch('https://example.com', {
credentials: 'same-origin'
})

Напротив, чтобы быть уверенным, что учётные данные не передаются с запросом, используйте credentials: ‘omit’:

fetch('https://example.com', {
credentials: 'omit'
})

Отправка данных в формате JSON

При помощи fetch() (en-US) можно отправлять POST-запросы в формате JSON.

const url = 'https://example.com/profile';
const data = { username: 'example' };

try {
const response = await fetch(url, {
method: 'POST', // или 'PUT'
body: JSON.stringify(data), // данные могут быть 'строкой' или {объектом}!
headers: {
'Content-Type': 'application/json'
}
});
const json = await response.json();
console.log('Успех:', JSON.stringify(json));
} catch (error) {
console.error('Ошибка:', error);
}

Загрузка файла на сервер

На сервер можно загрузить файл, используя комбинацию HTML-элемента <input type="file" />, FormData() и fetch().

const formData = new FormData();
const fileField = document.querySelector('input[type="file"]');

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

try {
const response = await fetch('https://example.com/profile/avatar', {
method: 'PUT',
body: formData
});
const result = await response.json();
console.log('Успех:', JSON.stringify(result));
} catch (error) {
console.error('Ошибка:', error);
}

Загрузка нескольких файлов на сервер

На сервер можно загрузить несколько файлов, используя комбинацию HTML-элемента <input type="file" multiple />, FormData() и fetch().

const formData = new FormData();
const photos = document.querySelector('input[type="file"][multiple]');

formData.append('title', 'Мой отпуск в Вегасе');
for (let i = 0; i < photos.files.length; i++) {
formData.append('photos', photos.files[i]);
}

try {
const response = await fetch('https://example.com/posts', {
method: 'POST',
body: formData
});
const result = await response.json();
console.log('Успех:', JSON.stringify(result));
} catch (error) {
console.error('Ошибка:', error);
}

Обработка текстового файла построчно

Фрагменты данных, получаемые из ответа, не разбиваются на строки автоматически (по крайней мере с достаточной точностью) и представляют собой не строки, а объекты Uint8Array. Если вы хотите загрузить текстовый файл и обрабатывать его по мере загрузки построчно, то на вас самих ложится груз ответственности за обработку всех упомянутых моментов. Как пример, далее представлен один из способов подобной обработки с помощью создания построчного итератора (для простоты приняты следующие допущения: текст приходит в кодировке UTF-8 и ошибки получения не обрабатываются).

async function* makeTextFileLineIterator(fileURL) {
const utf8Decoder = new TextDecoder("utf-8");
let response = await fetch(fileURL);
let reader = response.body.getReader();
let {value: chunk, done: readerDone} = await reader.read();
chunk = chunk ? utf8Decoder.decode(chunk) : "";

let re = /n|r|rn/gm;
let startIndex = 0;
let result;

for (;;) {
let result = re.exec(chunk);
if (!result) {
if (readerDone) {
break;
}
let remainder = chunk.substr(startIndex);
({value: chunk, done: readerDone} = await reader.read());
chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : "");
startIndex = re.lastIndex = 0;
continue;
}
yield chunk.substring(startIndex, result.index);
startIndex = re.lastIndex;
}
if (startIndex < chunk.length) {
//последняя строка не имеет символа перевода строки в конце
yield chunk.substr(startIndex);
}
}

for await (let line of makeTextFileLineIterator(urlOfFile)) {
processLine(line);
}

Проверка успешности запроса

В методе fetch() (en-US) promise будет отклонён (reject) с TypeError, когда случится ошибка сети или не будет сконфигурирован CORS на стороне запрашиваемого сервера, хотя обычно это означает проблемы доступа или аналогичные — для примера, 404 не является сетевой ошибкой. Для достоверной проверки успешности fetch() будет включать проверку того, что promise успешен (resolved), затем проверку того, что значение свойства Response.ok (en-US) является true. Код будет выглядеть примерно так:

try {
const response = await fetch('flowers.jpg');
if (!response.ok) {
throw new Error('Ответ сети был не ok.');
}
const myBlob = await response.blob();
const objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
} catch (error) {
console.log('Возникла проблема с вашим fetch запросом: ', error.message);
}

Составление своего объекта запроса

Вместо передачи пути ресурса, который вы хотите запросить вызовом fetch(), вы можете создать объект запроса, используя конструктор Request() (en-US), и передать его в fetch() аргументом:

const myHeaders = new Headers();

const myInit = {
method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default'
};

const myRequest = new Request('flowers.jpg', myInit);
const response = await fetch(myRequest);
const myBlob = await response.blob();
const objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;

Конструктор Request() принимает точно такие же параметры, как и метод fetch(). Вы даже можете передать существующий объект запроса для создания его копии:

const anotherRequest = new Request(myRequest, myInit);

Довольно удобно, когда тела запроса и ответа используются единожды (прим.пер.: «are one use only»). Создание копии как показано позволяет вам использовать запрос/ответ повторно, при изменении опций init, при желании. Копия должна быть сделана до прочтения тела, а чтение тела в копии также пометит его прочитанным в исходном запросе.

Примечание: Также есть метод clone() (en-US), создающий копии. Оба метода создания копии прекратят работу с ошибкой если тело оригинального запроса или ответа уже было прочитано, но чтение тела клонированного ответа или запроса не приведёт к маркировке оригинального.

Заголовки

Интерфейс Headers (en-US) позволяет вам создать ваш собственный объект заголовков через конструктор Headers() (en-US). Объект заголовков — простая мультикарта имён-значений:

const content = 'Hello World';
const myHeaders = new Headers();
myHeaders.append('Content-Type', 'text/plain');
myHeaders.append('Content-Length', content.length.toString());
myHeaders.append('X-Custom-Header', 'ProcessThisImmediately');

То же может быть достигнуто путём передачи массива массивов или литерального объекта конструктору:

const myHeaders = new Headers({
'Content-Type': 'text/plain',
'Content-Length': content.length.toString(),
'X-Custom-Header': 'ProcessThisImmediately'
});

Содержимое может быть запрошено и извлечено:

console.log(myHeaders.has("Content-Type")); // true
console.log(myHeaders.has("Set-Cookie")); // false
myHeaders.set("Content-Type", "text/html");
myHeaders.append("X-Custom-Header", "AnotherValue");

console.log(myHeaders.get("Content-Length")); // 11
console.log(myHeaders.get("X-Custom-Header")); // ["ProcessThisImmediately", "AnotherValue"]

myHeaders.delete("X-Custom-Header");
console.log(myHeaders.get("X-Custom-Header")); // [ ]

Некоторые из этих операций могут быть использованы только в ServiceWorkers (en-US), но они предоставляют более удобный API для манипуляции заголовками.

Все методы Headers выбрасывают TypeError, если имя используемого заголовка не является валидным именем HTTP Header. Операции мутации выбросят TypeError если есть защита от мутации (смотрите ниже) (прим.пер.: «if there is an immutable guard»). В противном случае они прерываются молча. Например:

const myResponse = Response.error();
try {
myResponse.headers.set('Origin', 'http://mybank.com');
} catch (e) {
console.log('Не могу притвориться банком!');
}

Хорошим вариантом использования заголовков является проверка корректности типа контента перед его обработкой. Например:

try {
const response = await fetch(myRequest);
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new TypeError("Ой, мы не получили JSON!");
}
const json = await response.json();
/_ Дальнейшая обработка JSON _/
} catch (error) {
console.log(error);
}

Защита

С тех пор как заголовки могут передаваться в запросе, приниматься в ответе и имеют различные ограничения в отношении того, какая информация может и должна быть изменена, заголовки имеют свойство guard. Это не распространяется на Web, но влияет на то, какие операции мутации доступны для объекта заголовков.

Возможные значения:

none: по умолчанию.request: защита объекта заголовков, полученного по запросу (Request.headers (en-US)).request-no-cors: защита объекта заголовков, полученного по запросу созданного с Request.mode no-cors.response: защита Headers полученных от ответа (Response.headers (en-US)).immutable: в основном, используется в ServiceWorkers; делает объект заголовков read-only.

Примечание: вы не можете добавить или установить request защищаемые Headers’ заголовок Content-Length. Аналогично, вставка Set-Cookie в заголовок ответа недопустимо: ServiceWorkers не допускают установки cookies через синтезированные ответы.

Объекты ответа

Как вы видели выше, экземпляр Response будет возвращён когда fetch() промис будет исполнен.

Свойства объекта-ответа которые чаще всего используются:

Response.status (en-US) — Целочисленное (по умолчанию 200) содержит код статуса ответа.Response.statusText (en-US) — Строка (по умолчанию»OK»), которая соответствует HTTP коду статуса.Response.ok (en-US) — как сказано ранее, это короткое свойство для упрощения проверки на то что статус ответа находится где-то между 200-299 включительно. Это свойство типа Boolean (en-US).

Они так же могут быть созданы с помощью JavaScript, но реальная польза от этого есть только при использовании сервис-воркеров (en-US), когда вы предоставляете собственный ответ на запрос с помощью метода respondWith() (en-US):

const myBody = new Blob();

addEventListener('fetch', function(event) {
// ServiceWorker перехватывает fetch
event.respondWith(
new Response(myBody, {
headers: { 'Content-Type': 'text/plain' }
})
);
});

Конструктор Response() принимает два необязательных аргумента — тело для ответа и объект init (аналогичный тому, который принимает Request() (en-US))

Примечание: Метод error() (en-US) просто возвращает ответ об ошибке. Аналогично, redirect() (en-US) возвращает ответ, приводящий к перенаправлению на указанный URL. Они также относятся только к Service Workers.

Тело

Запрос и ответ могут содержать данные тела. Тело является экземпляром любого из следующих типов:

ArrayBufferArrayBufferView (en-US) (Uint8Array и подобные)Blob/FilestringURLSearchParamsFormData

Body примесь определяет следующие методы для извлечения тела (реализованы как для Request так и для Response). Все они возвращают promise, который в конечном итоге исполняется и выводит содержимое.

arrayBuffer() (en-US)blob() (en-US)json() (en-US)text() (en-US)formData() (en-US)

Это делает использование нетекстовых данных более лёгким, чем при XMR.

В запросе можно установить параметры для отправки тела запроса:

const form = new FormData(document.getElementById('login-form'));
fetch('/login', {
method: 'POST',
body: form
});

Параметры request и response (and by extension the fetch() function), по возможности возвращают корректные типы данных. Параметр request также автоматически установит Content-Type в заголовок, если он не был установлен из словаря.

Функция обнаружения

Поддержка Fetch API может быть обнаружена путём проверки наличия Headers (en-US), Request, Response или fetch() (en-US) в области видимости Window или Worker. Для примера:

if (self.fetch) {
// запустить мой fetch запрос здесь
} else {
// Сделать что-то с XMLHttpRequest?
}

Полифил

BCD tables only load in the browser

Для того, чтобы использовать Fetch в неподдерживаемых браузерах, существует Fetch Polyfill , который воссоздаёт функциональность для не поддерживающих браузеров.

СпецификацииSpecification Status CommentFetch Живой стандарт Initial definitionСовместимость браузера

Смотрите такжеServiceWorker APIHTTP access control (CORS)HTTPFetch polyfillFetch examples on Github`

Table of Contents

In the previous article, I have explained how to deploy a Node.js application to Heroku.
In this tutorial, we will be making use of the endpoint created there and see if we can use it in our React project.

Project Setup

Let’s create a React project using the following command:

1npx create-react-app react-cors

Now update the App.js with the following code:

App.js

1import { useEffect, useState } from "react"

2import "./App.css"

3

4function App() {

5 const [message, setMessage] = useState("")

6 useEffect(() => {

7 fetch("https://nodejs-using-github.herokuapp.com/")

8 .then(response => response.json())

9 .then(data => {

10 setMessage(data.message)

11 })

12 .catch(err => console.log(err))

13 }, [])

14 return <div className="App">{message ? message : "Loading.."}</div>

15}

16

17export default App

Here we have a local state called message, which we show to the user.
If the message is empty, then we display them with a loading text.
When the component is mounted (useEffect), we make a call to the API endpoint and fetch the message.

Now let’s run this and see if it works:

loading

You will see that only «Loading..» text is displayed and the message never loads.
If we inspect the page and see the console, we will see the following error:

Access to fetch at 'https://nodejs-using-github.herokuapp.com/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

cors error

In the next sections, we will see what is CORS and how to fix this error.

What is CORS (Cross-Origin Resource Sharing)?

CORS stands for Cross-Origin Resource Sharing,
which is an HTTP header based mechanism that helps the server to tell the browser,
from which all domain requests can be made (except the same domain).

That is, in our case, the Node.js server hosted at https://nodejs-using-github.herokuapp.com/,
does not tell the browser that request can be made from http://localhost:3000.

When this happens, your browser will throw an error as seen earlier.

Why CORS (Cross-Origin Resource Sharing)?

The next question that would come to your mind is why do we really need this mechanism.
Imagine you are logged into your bank account or any social media website, then you visit a malicious website.
This malicious website could run some scripts in the background to make API calls to your banking or social media to get your personal details.

To prevent this, your browser checks if the request to the banking or social media server can be made from the malicious website and throws the CORS error.

So CORS exists to share certain resources between trusted third-parties (across different origins/domains), hence the name Cross-Origin Resource Sharing.

How to configure CORS in Node.js

Since we are clear about what and why is CORS required, let’s see how to enable CORS in the Node.js application.

You may clone the Node.js code from this repo.
Once the project is cloned, open it in your code editor and install cors package.

Now open index.js and update it with the following code:

index.js

1const express = require("express")

2const cors = require("cors")

3const app = express()

4const port = process.env.PORT || 3000

5

6const whitelist = ["http://localhost:3000"]

7const corsOptions = {

8 origin: function (origin, callback) {

9 if (!origin || whitelist.indexOf(origin) !== -1) {

10 callback(null, true)

11 } else {

12 callback(new Error("Not allowed by CORS"))

13 }

14 },

15 credentials: true,

16}

17app.use(cors(corsOptions))

18

19app.get("/", (req, res) => {

20 res.send({ message: "Hello World!" })

21})

22

23app.listen(port, () => {

24 console.log(`Example app listening at Port: ${port}`)

25})

Here we check if the origin (client’s domain) is in the whitelist, then we tell the clients that requests can be made.
If it is not in the list then we throw an error saying the client is not allowed to make CORS requests to this server.

The domain should not have any trailing slashes (/)

We can deploy the changes to Heroku and see if this works.

Now if you reload your page, you should be able to see the message.

cors success

You will also see that a response header called Access-Control-Allow-Origin has been added with the value http://localhost:3000.

Making CORS domains configurable

If you have multiple client origins to be connected to you, and you want them to be configurable, you can do so by using environment variables:

index.js

1const express = require("express")

2const cors = require("cors")

3const app = express()

4const port = process.env.PORT || 3000

5

6const domainsFromEnv = process.env.CORS_DOMAINS || ""

7

8const whitelist = domainsFromEnv.split(",").map(item => item.trim())

9

10const corsOptions = {

11 origin: function (origin, callback) {

12 if (!origin || whitelist.indexOf(origin) !== -1) {

13 callback(null, true)

14 } else {

15 callback(new Error("Not allowed by CORS"))

16 }

17 },

18 credentials: true,

19}

20app.use(cors(corsOptions))

21

22app.get("/", (req, res) => {

23 res.send({ message: "Hello World!" })

24})

25

26app.listen(port, () => {

27 console.log(`Example app listening at Port: ${port}`)

28})

Testing environment variables locally

To test environment variables locally, you can install the package called dotenv:

Now create a file called .env in the root directory of your project with the domains:

1CORS_DOMAINS = http://localhost:3000, http://localhost:3001, https://example.com

Update index.js to use the dotenv package:

index.js

1const express = require("express")

2const cors = require("cors")

3const app = express()

4const port = process.env.PORT || 3000

5

6if (process.env.NODE_ENV !== "production") {

7 require("dotenv").config()

8}

9

10const domainsFromEnv = process.env.CORS_DOMAINS || ""

11

12const whitelist = domainsFromEnv.split(",").map(item => item.trim())

13

14const corsOptions = {

15 origin: function (origin, callback) {

16 if (!origin || whitelist.indexOf(origin) !== -1) {

17 callback(null, true)

18 } else {

19 callback(new Error("Not allowed by CORS"))

20 }

21 },

22 credentials: true,

23}

24app.use(cors(corsOptions))

25

26app.get("/", (req, res) => {

27 res.send({ message: "Hello World!" })

28})

29

30app.listen(port, () => {

31 console.log(`Example app listening at Port: ${port}`)

32})

Here we made sure that .env files are loaded only in non-production environments.
It is recommended to store the configurations in the server host rather than in .env files for production.

Remember to add .env* to the .gitignore file so that you don’t accidentally push them to the repo.

Configuring environment files in heroku

With our latest code, we can configure environment files in the heroku settings:

heroku env

Go to your project settings and click on «Reveal Config Vars». Now you can provide the key and values here and click on «Add»

heroku config vars

Once added, you can push your changes and see if the changes work.

If you have liked article, stay in touch with me by following me on twitter.

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

А вот еще интересные материалы:

  • Яшка сломя голову остановился исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного где ошибка
  • Ошибка clr 80070005 работа программы будет прекращена discord
  • Ошибка corona error message