Express Server 无法从 React Native 应用程序获取请求

Posted

技术标签:

【中文标题】Express Server 无法从 React Native 应用程序获取请求【英文标题】:Express Server cannot GET request from the React Native app 【发布时间】:2020-08-04 00:56:36 【问题描述】:

目标:创建一个可以相互通信的前端和后端。 我从前端开始,使用https://reactnative.dev/movies.json 作为占位符来提供前端数据。一旦成功完成,我就开始开发服务器,即使它在 Postman 中工作,它在前端也不能工作。

问题:当我将https://reactnative.dev/movies.json 替换为http://xxx.xxx.xxx.x:5000/movies.json 时,后端和前端无法通信。

到目前为止的测试: 我的 react native 应用可以成功GEThttps://reactnative.dev/movies.json 构建的 reactnative.dev JSON 电影列表,并会显示电影列表。

我已经使用 Postman 测试了后端,并验证了 http://xxx.xxx.xxx.x:5000/movies.json 返回的 JSON 响应与 https://reactnative.dev/movies.json 完全相同

前端(工作 - 可以 GET 来自 https://reactnative.dev/movies.json):

import React from 'react';
import  StyleSheet, Text, View, ActivityIndicator, Button  from 'react-native';

export default class App extends React.Component 

  constructor(props) 
    super(props);
    this.state = 
        isLoading: true,
        dataSource: null,
    ;


  componentDidMount()
    return fetch('https://reactnative.dev/movies.json')
    .then((response) => response.json())
    .then((responseJson) => 
      this.setState(
        isLoading: false,
        dataSource: responseJson.movies,
      )
    )
    .catch((error) => 
      console.log(error);
    );
  

  render () 
    if(this.state.isLoading) 
      return (
      <View style=styles.container> 
        <ActivityIndicator/>
      </View>
      )
     else 
      let movies = this.state.dataSource.map((val, key) => 
        return <View key=key style=styles.item>
          <Text>val.title</Text>
        </View>
      );
      return (
        <View style=styles.container> 
          movies
          
        </View>
        )     
    
  


const styles = StyleSheet.create(
  container: 
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  ,
);

服务器结构:

/backend
--/data
  --movies.json
--/routes
  --movies.js
  --routes.js
server.js
package.json
package-lock.json
server.cert
server.key
.env
.gitignore

/data/movies.json


    "title": "The Basics - Networking",
    "description": "Your app fetched this from a remote endpoint!",
    "movies": [
       "id": "1", "title": "Star Wars", "releaseYear": "1977" ,
       "id": "2", "title": "Back to the Future", "releaseYear": "1985" ,
       "id": "3", "title": "The Matrix", "releaseYear": "1999" ,
       "id": "4", "title": "Inception", "releaseYear": "2010" ,
       "id": "5", "title": "Interstellar", "releaseYear": "2014" 
    ]

/routes/movies.js

const userRoutes = (app, fs) => 
    // variables
    const dataPath = "./data/movies.json";
    
    // READ
    app.get("/movies.json", (req, res) => 
        fs.readFile(dataPath, "utf8", (err, data) => 
        if (err) 
            throw err;
        
        res.send(JSON.parse(data));
        );
    );
;

module.exports = userRoutes;

/routes/routes.js

// load up our shiny new route for users
const userRoutes = require("./movies");

const appRouter = (app, fs) => 
  // we've added in a default route here that handles empty routes
  // at the base API url
  app.get("/", (req, res) => 
    res.send("welcome to the development api-server");
  );

  // run our user route module here to complete the wire up
  userRoutes(app, fs);
;

// this line is unchanged
module.exports = appRouter;

server.js

// load up the express framework and body-parser helper
const express = require("express");
const bodyParser = require("body-parser");

// create an instance of express to serve our end points
const app = express();

// we'll load up node's built in file system helper library here
// (we'll be using this later to serve our JSON files
const fs = require("fs");

// configure our express instance with some body-parser settings
// including handling JSON data
app.use(bodyParser.json());
app.use(bodyParser.urlencoded( extended: true ));

// this is where we'll handle our various routes from
const routes = require("./routes/routes.js")(app, fs);

// finally, launch our server on port 5000.
const server = app.listen(5000, () => 
  console.log("listening on port %s...", server.address().port);
);

控制台错误输出:

Network request failed
- node_modules\whatwg-fetch\dist\fetch.umd.js:511:17 in setTimeout$argument_0
- node_modules\react-native\Libraries\Core\Timers\JSTimers.js:135:14 in _callTimer
- node_modules\react-native\Libraries\Core\Timers\JSTimers.js:387:16 in callTimers
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:425:19 in __callFunction
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:112:6 in __guard$argument_0- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:373:10 in __guard
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:111:4 in callFunctionReturnFlushedQueue
* [native code]:null in callFunctionReturnFlushedQueue

更新了服务器以使用 HTTPS:

// load up the express framework and body-parser helper
const express = require("express");
const bodyParser = require("body-parser");
// create an instance of express to serve our end points
const app = express();

// we'll load up node's built in file system helper library here
// (we'll be using this later to serve our JSON files
const fs = require("fs");
const https = require('https');

// configure our express instance with some body-parser settings
// including handling JSON data
app.use(bodyParser.json());
app.use(bodyParser.urlencoded( extended: true ));

// this is where we'll handle our various routes from
const routes = require("./routes/routes.js")(app, fs);

// finally, launch our server on port 5000.
const server = https.createServer(
  key: fs.readFileSync('server.key'),
  cert: fs.readFileSync('server.cert')
, app).listen(5000, () => 
  console.log("listening on port %s...", server.address().port);
);

HTTPS 更新后的控制台错误输出:

Network request failed
- node_modules\whatwg-fetch\dist\fetch.umd.js:505:17 in setTimeout$argument_0
- node_modules\react-native\Libraries\Core\Timers\JSTimers.js:135:14 in _callTimer
- node_modules\react-native\Libraries\Core\Timers\JSTimers.js:387:16 in callTimers
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:425:19 in __callFunction
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:112:6 in __guard$argument_0- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:373:10 in __guard
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:111:4 in callFunctionReturnFlushedQueue
* [native code]:null in callFunctionReturnFlushedQueue

在 Postman 中对 HTTPS 服务器的测试结果: GET 请求有效,但是我必须将 Postman 设置为接受自签名证书。

我运行expo eject 来公开整个Info.plist NSAppTransportSecurity 的键和字典已经在Info.plist 中设置在下面:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>CFBundleDisplayName</key>
    <string>csharp_frontend</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0.0</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>NSAppTransportSecurity</key>
    <dict>
      <key>NSAllowsArbitraryLoads</key>
      <true/>
      <key>NSExceptionDomains</key>
      <dict>
        <key>xxx.xxx.xxx.x</key>
        <dict>
          <key>NSExceptionAllowsInsecureHTTPLoads</key>
          <true/>
        </dict>
      </dict>
    </dict>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string/>
    <key>UILaunchStoryboardName</key>
    <string>SplashScreen</string>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
      <string>armv7</string>
    </array>
    <key>UISupportedInterfaceOrientations</key>
    <array>
      <string>UIInterfaceOrientationPortrait</string>
      <string>UIInterfaceOrientationPortraitUpsideDown</string>
    </array>
    <key>UIViewControllerBasedStatusBarAppearance</key>
    <false/>
    <key>UIStatusBarStyle</key>
    <string>UIStatusBarStyleDefault</string>
    <key>UIRequiresFullScreen</key>
    <true/>
  </dict>
</plist>

更新了 Info.plist 以包含服务器的 IP 地址而不是 localhost

      <dict>
        <key>xxx.xxx.xxx.x</key>
        <dict>
          <key>NSExceptionAllowsInsecureHTTPLoads</key>
          <true/>
        </dict>
      </dict>

错误略有改变 - 新错误 - '373:10 in __guard'

Network request failed
- node_modules\whatwg-fetch\dist\fetch.umd.js:505:17 in setTimeout$argument_0
- node_modules\react-native\Libraries\Core\Timers\JSTimers.js:135:14 in _callTimer
- node_modules\react-native\Libraries\Core\Timers\JSTimers.js:387:16 in callTimers
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:425:19 in __callFunction
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:112:6 in __guard$argument_0
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:373:10 in __guard
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:111:4 in callFunctionReturnFlushedQueue
* [native code]:null in callFunctionReturnFlushedQueue

可能是问题:我认为由于 SSL 证书是自签名的,因此反应原生前端将请求视为 HTTP 而不是 HTTPS。

有什么想法吗??

【问题讨论】:

console.log(error) 显示什么? xxx.xxx.xxx.x 是 IP 地址吗?如果是,移动客户端是否在同一个网络上 也可能是因为你已经从 HTTPS 转到了 HTTP ~ github.com/facebook/react-native/issues/24408 嗨@Phil,我刚刚在console.log(error) 输出中添加。是的xxx.xxx.xxx.x 代表我的IP 地址,是的,移动客户端在同一个网络上。嗯,这是一个很好的发现,我在 ios 上,可能是同样的问题,如果这是解决方案,我会调查并更新问题。谢谢。 【参考方案1】:

如果您在 android 平台上运行并使用 API 级别 > 27,那么我认为这个问题与 Android 中的 http 协议限制有关。如果是这样,您需要将 android:usesCleartextTraffic="true" 添加到 AndroidManifest.xml 以允许 http 流量:

<?xml version="1.0" encoding="utf-8"?>
<manifest...>
    <application
        ...
        android:usesCleartextTraffic="true"
        ...>
    </application>
</manifest>

如果是 iOS 版本 >= 8

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
        ...
    </dict>

【讨论】:

好的,没看到那条评论。您使用的是哪个 iOS 版本? 嗨@TuanDucVo iOS 软件版本 13.5.1 @DotNetFullStack 您需要添加以下行:&lt;key&gt;NSAllowsArbitraryLoads&lt;/key&gt; &lt;true/&gt; 到 info.plist 目前正在调查***.com/questions/48157185/… @DotNetFullStack 你在 react native 项目中看到那个文件了吗?

以上是关于Express Server 无法从 React Native 应用程序获取请求的主要内容,如果未能解决你的问题,请参考以下文章

Node.js - 在 Server.js 上执行 Node 命令(Express + React + Webpack)

无法从 Express 中捕获 React 中的错误

无法从 apollo-server-express 导入 SchemaDirectiveVisitor

无法将多部分表单数据从 React 正确发送到 Express Js

React app Express Api无法运行localhost 404(未找到)

如何从 express 重定向到 react-router?