Эта статья была создана в сотрудничестве с IP2Location . Спасибо за поддержку партнеров, которые делают возможным использование SitePoint.
В мире, где онлайн-коммерция стала нормой, нам нужно создавать более быстрые, удобные и безопасные веб-сайты, чем когда-либо. В этой статье вы узнаете, как настроить веб-сайт на базе Node.js, способный направлять трафик на релевантные целевые страницы в зависимости от страны посетителя. Вы также узнаете, как блокировать анонимный трафик (например, Tor), чтобы устранить риски, связанные с такими сетями.
Для реализации этих функций мы будем использовать веб-сервис IP2Proxy , предоставляемый IP2Location , поставщиком решений Geo IP. Веб-сервис — это REST API, который принимает IP-адрес и отправляет данные геолокации в формате JSON.
Вот некоторые из полей, которые мы получим:
- название страны
- название города
- isProxy
- proxyType
- и т.п.
Мы будем использовать Next.js для создания веб-сайта, содержащего следующие целевые страницы:
- Домашняя страница : загрузка и перенаправление API будут запускаться с этой страницы
- Целевая страница : поддерживаемые страны увидят страницу продукта в местной валюте
- Страница недоступна : другие страны увидят эту страницу с возможностью присоединиться к списку ожидания
- Страница злоупотребления : посетители, использующие сети Tor, будут перенаправлены на эту страницу
Теперь, когда вы знаете план проекта, давайте посмотрим, что вам нужно для начала.
Предпосылки
На вашей машине я очень рекомендую следующее:
- Последняя LTS-версия Node.js (v12)
- пряжа
Подойдет более старая версия Node.js, но самая последняя версия LTS (долгосрочная поддержка) содержит улучшения производительности и отладки в области асинхронного кода, с которым мы будем иметь дело. Пряжа не нужна, но вы выиграете от ее более высокой производительности, если будете ее использовать.
Я также собираюсь предположить, что у вас есть хороший фундамент в:
Как упоминалось ранее, мы будем использовать Next.js для создания нашего сайта. Если вы новичок в этом, вы можете следовать их официальному интерактивному учебнику, чтобы быстро освоиться.
IP2Location + Next.js Краткое руководство по проекту
Настройка проекта
Чтобы настроить проект, просто запустите терминал и перейдите в рабочее пространство. Выполните следующую команду:
npx create-next-app
Не стесняйтесь давать вашему приложению любое имя. Я назвал мой next-ip2location-example
. После завершения установки перейдите к корню проекта и выполните команду yarn dev
. Это запустит сервер разработки Node.js. Если вы откроете браузер и перейдете на localhost:3000
, вы должны увидеть страницу с заголовком «Welcome to Next.js». Это должно подтвердить, что у нас есть работающее приложение, которое работает без ошибок. Остановите приложение и установите следующие зависимости:
yarn add yarn add next-compose-plugins dotenv-load next-env @zeit/next-css bulma isomorphic-unfetch
Мы будем использовать фреймворк Bulma CSS для добавления готовых стилей для нашего сайта. Поскольку мы будем подключаться к службе API, мы .env
файл .env
для хранения нашего ключа API. Обратите внимание, что этот файл не должен храниться в хранилище. Затем создайте файл next.config.js.
в корне проекта и добавьте следующий код:
const withPlugins = require('next-compose-plugins') const css = require('@zeit/next-css') const nextEnv = require('next-env') const dotenvLoad = require('dotenv-load') dotenvLoad() module.exports = withPlugins([ nextEnv(), [css] ])
Приведенная выше конфигурация позволяет нашему приложению читать файл .env
и загружать значения. Обратите внимание, что ключи должны иметь префикс NEXT_SERVER_
для загрузки в серверной среде. Посетите страницу пакета next-env для получения дополнительной информации. Мы установим ключ API в следующем разделе. Приведенная выше конфигурация также дает нашему приложению Next.js возможность предварительно обрабатывать код CSS с помощью пакета zeit/next-css
. Это позволит нам использовать фреймворк Bulma CSS в нашем приложении. Обратите внимание, что нам нужно импортировать код Bulma CSS в наше приложение Next.js. Я скоро покажу вам, где это сделать.
Получение ключа API для веб-службы IP2Proxy
Как упоминалось ранее, нам нужно преобразовать IP-адрес посетителя в информацию, которую мы можем использовать для перенаправления или блокировки трафика. Просто перейдите по следующей ссылке и подпишитесь на бесплатный пробный ключ:
После регистрации вы получите бесплатный ключ API по электронной почте. Создайте файл .env
и поместите его в корень папки вашего проекта. Скопируйте ключ API в файл следующим образом:
NEXT_SERVER_IP2PROXY_API=<place API key here>
Этот бесплатный ключ даст вам 1000 бесплатных кредитов. Как минимум, нам понадобятся следующие поля для работы нашего приложения:
- название страны
- proxyType
Если вы посмотрите на раздел цен на странице IP2Proxy, вы заметите, что пакет PX2
даст нам требуемый ответ. Это означает, что каждый запрос будет стоить нам два кредита. Ниже приведен пример того, как должен быть создан URL:
-
http://api.ip2proxy.com/?ip=8.8.8.8&key=demo&package=PX2
Вы также можете отправить запрос URL без IP. Служба будет использовать IP-адрес компьютера, отправившего запрос. Мы также можем использовать пакет PX8
, чтобы получить все доступные поля, такие как isp
и domain
в самом верхнем пакете веб-службы обнаружения IP2Proxy.
-
http://api.ip2proxy.com/?key=demo&package=PX8
В следующем разделе мы создадим простую систему управления состоянием для хранения прокси-данных, которые будут доступны всем страницам сайта.
Создание контекстного API в Next.js
Создайте файл context/proxy-context
и вставьте следующий код:
import React, { useState, useEffect, useRef, createContext } from 'react' export const ProxyContext = createContext() export const ProxyContextProvider = (props) => { const initialState = { ipAddress: '0.0.0.0', countryName: 'Nowhere', isProxy: false, proxyType: '' } // Declare shareable proxy state const [proxy, setProxy] = useState(initialState) const prev = useRef() // Read and Write Proxy State to Local Storage useEffect(() => { if (proxy.countryName == 'Nowhere') { const localState = JSON.parse(localStorage.getItem('ip2proxy')) if (localState) { console.info('reading local storage') prev.current = localState.ipAddress setProxy(localState) } } else if (prev.current !== proxy.ipAddress) { console.info('writing local storage') localStorage.setItem('ip2proxy', JSON.stringify(proxy)) } }, [proxy]) return( <ProxyContext.Provider value={[ipLocation, setProxy]}> {props.children} </ProxyContext.Provider> ) }
По сути, мы объявляем разделяемое состояние, называемое proxy
котором будут храниться данные, полученные из веб-службы IP2Proxy. Запрос извлечения API будет реализован в pages/index.js
. Информация будет использоваться для перенаправления посетителей на соответствующие страницы. Если посетитель попытается обновить страницу, сохраненное состояние будет потеряно. Чтобы этого не произошло, мы будем использовать useEffect()
для сохранения состояния в локальном хранилище браузера. Когда пользователь обновляет определенную целевую страницу, состояние прокси-сервера будет получено из локального хранилища, поэтому нет необходимости повторять запрос. Вот краткий обзор локального хранилища Chrome в действии:
Совет: Если вы столкнетесь с проблемами в этом руководстве, очистка локального хранилища может помочь решить некоторые проблемы.
Отображение информации прокси
Создайте файл components/proxy-view.js
и добавьте следующий код:
import React, { useContext } from 'react' import { ProxyContext } from '../context/proxy-context' const style = { padding: 12 } const ProxyView = () => { const [proxy] = useContext(ProxyContext) const { ipAddress, countryName, isProxy, proxyType } = proxy return ( <div className="box center" style={style}> <div className="content"> <ul> <li>IP Address : {ipAddress} </li> <li>Country : {countryName} </li> <li>Proxy : {isProxy} </li> <li>Proxy Type: {proxyType} </li> </ul> </div> </div> ) } export default ProxyView
Это просто компонент отображения, который мы будем размещать в конце каждой страницы. Мы создаем это только для того, чтобы подтвердить, что логика выборки и состояние приложения работают должным образом. Обратите внимание, что строка const [proxy] = useContext(ProxyContext)
не будет работать, пока мы не const [proxy] = useContext(ProxyContext)
наш Context Provider
в корне нашего приложения. Давайте сделаем это сейчас в следующем разделе.
Реализация провайдера контекстного API в приложении Next.js
Создайте файл pages/_app.js
и добавьте следующий код:
import React from 'react' import App from 'next/app' import 'bulma/css/bulma.css' import { ProxyContextProvider } from '../context/proxy-context' export default class MyApp extends App { render() { const { Component, pageProps } = this.props return ( <ProxyContextProvider> <Component {...pageProps} /> </ProxyContextProvider> ) } }
Файл _app.js
является корневым компонентом нашего приложения Next.js, где мы можем делиться глобальным состоянием с остальными страницами сайта и дочерними компонентами. Обратите внимание, что именно здесь мы импортируем CSS для платформы Bulma, которую мы установили ранее. С этой настройкой давайте теперь создадим макет, который мы будем использовать для всех страниц нашего сайта.
Построение шаблона макета
Создайте layout
папки в корне вашего проекта. Давайте переместим файл components/nav.js
в layout/nav.js
Замените текущий код на это:
import React from 'react' import Link from 'next/link' const Nav = () => ( <nav className="navbar" role="navigation" aria-label="main navigation"> <div className="navbar-brand"> <a href="/" className="navbar-item"><strong>Product Store</strong></a> </div> <div className="navbar-menu"> <a className="navbar-item" href="/">Home</a> <Link href='/landing'> <a className="navbar-item">Landing</a> </Link> <Link href='/unavailable'> <a className="navbar-item">Unavailable</a> </Link> <Link href='/abuse'> <a className="navbar-item">Abuse</a> </Link> </div> </nav> ) export default Nav
Обратите внимание, что это неполное навигационное меню, так как оно должно быть полностью адаптивным. Пожалуйста, посмотрите документацию Navbar, чтобы добавить поддержку для планшетов и мобильных экранов.
Я также хотел бы отметить, что ссылка Home
не использует компонент Link
. Я сделал это намеренно, чтобы, когда пользователь нажимает на него, он вызывал запрос GET сервера. Остальные ссылки будут выполнять только навигацию на стороне клиента.
Затем создайте файл layout/layout.js
и добавьте следующий код:
import Head from 'next/head' import Nav from './nav' import ProxyView from '../components/proxy-view' const Layout = (props) => ( <div> <Head> <title>IP2Location Example</title> <link rel='icon' href='/favicon.ico' /> </Head> <Nav /> <section className="section"> <div className="container"> {props.children} <ProxyView /> </div> </section> </div> ) export default Layout
Теперь, когда мы определили Layout
, давайте начнем создавать страницы нашего сайта, начиная с домашней страницы.
Создание нашей домашней страницы
Здесь мы выполним наш запрос API для веб-службы IP2Proxy. Мы сохраним полученный ответ в нашем состоянии ProxyContext
. Во-первых, мы быстро создадим только пользовательский интерфейс. Откройте файл pages/index.js
и замените существующий код следующим:
import Head from 'next/head' import Layout from '../layout/layout' const Home = () => { return ( <Layout> <Head> <title>Home</title> </Head> <section className="hero is-light"> <div className="hero-body"> <div className="container"> <h1 className="title">Home</h1> <h2 className="subtitle">Checking availability in your country...</h2> </div> </div> </section> </Layout> ) } export default Home
Сейчас самое время запустить сервер Next.js dev с помощью команды yarn yarn dev
или npm run dev
. Вы должны получить следующий вывод:
Обратите внимание, что компонент ProxyView
отображает начальные значения, которые мы установили в ProxyContextProvider
. В следующем разделе мы выполним действие извлечения и обновим эти значения.
Выполнение запроса на выборку в веб-службе IP2Proxy
В этом разделе мы напишем асинхронную функцию для выполнения запроса выборки API. Мы сделаем это внутри функции getInitialProps . Результаты будут переданы компоненту Home
где они будут сохранены в глобальном состоянии proxy
через ProxyContext
. Кроме того, мы будем использовать встроенную страницу ошибок для отображения ошибок, обнаруженных нашим кодом. Во-первых, давайте определим функцию getInitialProps
, обновив код в pages/index.js
:
import fetch from 'isomorphic-unfetch' //const Home...{} Home.getInitialProps = async ({ req }) => { if (req) { // This code'll run in server mode const api_key = process.env.NEXT_SERVER_IP2PROXY_API || 'demo' const ipAddress = req.headers['x-forwarded-for'] || req.connection.remoteAddress const localAddresses = ['::1', '127.0.0.1', 'localhost'] // Construct IP2Proxy web service URL let proxyUrl = `https://api.ip2proxy.com/?key=${api_key}&package=PX2` // If ipAddress is not localhost, add it to the URL as a parameter if (!localAddresses.includes(ipAddress)) proxyUrl = proxyUrl.concat(`&ip=${ipAddress}`) try { const response = await fetch(proxyUrl) const json = await response.json() console.log(json) if (json.response != 'OK') return { errorCode: 500, errorMessage: json.response } const { isProxy, proxyType, countryName } = json const newProxy = { ipAddress, isProxy, proxyType, countryName } return{ newProxy } } catch (error) { return { errorCode: error.code, errorMessage: error.message.replace(api_key, 'demo') } } } return { newProxy: null } // This line will run in client mode } export default Home
Далее давайте обновим наш компонент Home:
import React, { useContext, useEffect, } from 'react' import Error from 'next/error' import { ProxyContext } from '../context/proxy-context' const Home = ({newProxy, errorCode, errorMessage}) => { // Display error messages if (errorCode) { return <Error statusCode={errorCode} title={errorMessage} /> } // Save new proxy state const [proxy, setProxy] = useContext(ProxyContext) useEffect(() => { let ignore = false if(newProxy && !ignore) { setProxy(newProxy) } return () => { ignore = true; } }, [newProxy]) // return (... }
После сохранения изменений обновите страницу. Теперь вы должны увидеть поля в обновлении компонента ProxyView
. Обратите внимание на вывод, отображаемый в терминале:
{ response: 'OK', countryCode: 'KE', countryName: 'Kenya', proxyType: '-', isProxy: 'NO' }
Приведенная выше информация должна отражать страну, в которой вы находитесь. Следующим шагом будет перенаправление, но давайте сначала создадим недостающие целевые страницы.
Создание целевых страниц
Целевые страницы довольно просты в создании, так как имеют минимальную логику. Используйте меню навигации, чтобы подтвердить правильность рендеринга каждой страницы после добавления приведенного ниже кода для каждого из файлов:
pages / landing.js :
import React, { useContext } from 'react' import Head from 'next/head' import Layout from '../layout/layout' import { ProxyContext } from '../context/proxy-context' const Landing = () => { let localPrice = 25000 // Kenyan Shilling let exchangeRate = 0; let currencySymbol = '' const [proxy] = useContext(ProxyContext) const { countryName } = proxy switch (countryName) { case 'Kenya': exchangeRate = 1; currencySymbol = 'KShs.' break; case 'United Kingdom': currencySymbol = '£' exchangeRate = 0.0076; break; default: break; } // Format localPrice to currency format localPrice = (localPrice * exchangeRate).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,') return ( <Layout> <Head> <title>Landing</title> </Head> <section className="hero is-warning"> <div className="hero-body"> <div className="container"> <h1 className="title">Landing Page</h1> <h2 className="subtitle">Product is available in {countryName}!</h2> <button className="button is-link"><strong>Order Now</strong> - {currencySymbol} {localPrice} </button> </div> </div> </section> </Layout> ) } export default Landing
pages / unavailable.js :
import React, { useContext } from 'react' import Head from 'next/head' import Layout from '../layout/layout' import { ProxyContext } from '../context/proxy-context' const Unavailable = () => { const [proxy] = useContext(ProxyContext) const { countryName } = proxy return ( <Layout> <Head> <title>Unavailable</title> </Head> <section className="hero is-dark"> <div className="hero-body"> <div className="container"> <h1 className="title">Sorry. Product is not available in <strong>{countryName}</strong> </h1> <h2 className="subtitle">Click to join waiting list</h2> <button className="button is-link">Subscribe to Alert Notification</button> </div> </div> </section> </Layout> ) } export default Unavailable
pages / abuse.js :
import React from 'react' import Head from 'next/head' import Layout from '../layout/layout' const Abuse = () => ( <Layout> <Head> <title>Abuse</title> </Head> <section className="hero is-danger"> <div className="hero-body"> <div className="container"> <h1 className="title">Sorry! TOR Visitors not allowed</h1> <h2 className="subtitle">As much as we respect individual privacy, we would rather protect ourselves from users abusing privacy networks </h2> </div> </div> </section> </Layout> ) export default Abuse
Теперь, когда мы реализовали все наши целевые страницы, нам нужно автоматически перенаправить их. Перейдите к следующему разделу.
Переадресация трафика на соответствующие целевые страницы
Чтобы выполнить перенаправление в Next.js, нам потребуется использовать useRouter
из пакета next/router
, чтобы получить доступ к компоненту router
. Мы будем использовать функцию router.replace()
для выполнения перенаправления. Нам нужно определить два действия перенаправления:
- Первое перенаправление предназначено для блокировки трафика Tor и перехода на страницу «злоупотреблений».
- Второе перенаправление предназначено для перенаправления трафика на «целевые» или «недоступные» страницы.
Откройте pages/index.js
и добавьте следующий код:
import { useRouter } from 'next/router' const Home = ({newProxy, errorCode, errorMessage}) => { //... // Declare router const router = useRouter(); // Redirect if Proxy Type is TOR useEffect(() => { if (proxy.proxyType == 'TOR') { router.replace('/abuse') } }, [proxy]) // Redirect based on visitor's country const { countryName } = proxy useEffect(() => { if (countryName != 'Nowhere' && newProxy.proxyType !== 'TOR') { redirectPage(router, countryName) } }, [proxy]); //... } const redirectPage = (router, countryName) => { let redirectPage; switch (countryName) { case 'Kenya': // Replace with your country's name redirectPage = '/landing' break case 'United Kingdom': redirectPage = '/landing' break default: redirectPage = '/unavailable' } router.replace(redirectPage) }
В функции redirectPage
замените «Кения» на название вашей страны. Вы можете добавить больше стран, если хотите. Однако в целях тестирования нам нужно ограничить количество стран, поскольку существует ограничение на число стран, поддерживаемых прокси-сервисом. Говоря о тестировании, мы должны сначала развернуть наше приложение.
Совет. Если вы хотите выполнить тестирование вручную без развертывания, просто присвойте константе ipAddress
IP-адрес из другой страны. Вы также можете проверить IP-адреса TOR, выбрав один из этого списка IP-адресов Tor .
Развертывание нашего приложения Next.js
Самый простой и быстрый способ развертывания нашего приложения Next.js на рабочем сервере — это использование бессерверной платформы развертывания Zeit . Все, что вам нужно сделать, чтобы начать, это установить их сейчас CLI . Вам нужно будет только подтвердить свой адрес электронной почты, чтобы использовать бесплатный сервис.
Если бы мы развернули наше приложение сейчас, оно бы запустилось так, как ожидалось. Тем не менее, он достигнет 20 бесплатных кредитных лимитов, так как мы разработали наше приложение для использования demo
ключа, если мы не указали ключ API. Чтобы загрузить наш ключ сейчас, просто выполните следующую команду:
now secrets add NEXT_SERVER_IP2PROXY_API <ip2proxy api key>
Вы можете узнать больше об управлении переменными и секретами окружения на панели инструментов Zeit здесь . С этим определением, развертывание нашего приложения так же просто, как выполнение следующей команды:
now --prod
Команда автоматически запустит процесс сборки, а затем развернет его на серверах Zeit. Весь процесс должен длиться меньше минуты. URL производственного сервера будет автоматически скопирован в ваш буфер обмена. Просто откройте новую вкладку в вашем браузере и вставьте URL. Вы должны увидеть что-то похожее на ниже:
Тестирование сайта с помощью бесплатных прокси-сервисов
Чтобы подтвердить возможность перенаправления нашего приложения в зависимости от страны происхождения, мы собираемся использовать бесплатный прокси-сервис для эмуляции различных местоположений. Просто введите общедоступный URL-адрес вашего приложения и выберите сервер или оставьте его наугад.
В моем случае страны Кения и Великобритания будут перенаправлять на целевую страницу.
Любая другая страна будет перенаправлена на страницу «недоступно».
Тестирование сайта с браузером Tor
Давайте теперь посмотрим, может ли наш сайт блокировать трафик, исходящий из сетей Tor. Просто посетите веб-сайт Tor и установите браузер Tor для вашей платформы. Вот скриншот того, как должен выглядеть сайт:
Совет: Если у вас возникли трудности с установкой Tor на ваш компьютер, вам может быть проще установить и запустить браузер Tor на вашем устройстве Android или IOS.
Опция локальной базы данных
Прежде чем мы закончим эту статью, я хотел бы упомянуть, что IP2Location предлагает версии баз данных своих веб-сервисов. Он поставляется в виде файла CSV, который можно импортировать в любую систему баз данных, которую вы предпочитаете. Вместо того, чтобы запрашивать удаленный веб-сервис, вы можете создать свой собственный локальный веб-сервис, используя базу данных, что должно привести к более быстрому ответу. Обратите внимание, что при покупке базы данных она обновляется ежедневно. Следовательно, вам необходимо автоматизировать процесс загрузки и импорта новых данных в вашу локальную базу данных на ежедневной основе.
Вы задаетесь вопросом, как мы собираемся развернуть базу данных в архитектуре без серверов? Это довольно просто. Разверните свою базу данных в облачной службе, такой как MongoDb или FaunaDb . Однако развертывание базы данных на другом сервере сводит на нет преимущества наличия локальной базы данных. Я рекомендую использовать контейнеры Docker для упаковки и развертывания вашего приложения и базы данных на одном сервере или в центре обработки данных, чтобы получить преимущества в скорости.
Резюме
Я надеюсь, что теперь у вас есть уверенность в создании сайта, который может перенаправлять пользователей на соответствующие целевые страницы в зависимости от страны, из которой они просматривают. С помощью информации, предоставляемой сервисами IP2Location, вы можете продвинуть свой сайт на несколько шагов дальше:
- предлагая разные купоны или предложения в разных географических точках
- осуществление обнаружения мошенничества с кредитными картами путем сравнения местоположения посетителя с фактическим географическим адресом владельца карты.
Если вы посмотрите на веб-сервис IP2Location , вы заметите, что он предлагает другой набор полей геолокации, которые вы можете использовать в своем приложении. Если вы хотите объединить веб-сервисы IP2Location и IP2Proxy в одном приложении, вы можете посмотреть на этот проект, который я создал ранее, и который покажет вам, как это делается.