[ 공모전 ] 지도 페이지 : Mixed Content Error 해결방법들과 Nextjs rewrite, redirect

최문길·2024년 7월 14일
1

공모전

목록 보기
27/46

이전 포스팅에서 내 상황에서 해결할 수 있는 조합은

  • Next js에서는 api router를 통해 proxy server와 같은 역할을 해주며,
    CORS, 그리고 masking 하여 보안상이유로 사용 할 수 있는 Node기반
    serverless Function이다.
  • Nextjs에서는 사용자가 어떤 url로 접근시 특정 사이트로 옮겨주는 자체 기능이 있는데
    rewrite와 redirect 이다.

api router와 rewrite를 적절히 같이 사용하면 해결이 가능 할 것이라 적었다.
이제는 직접 해보면서 해결을 해보자.


api router와 rewrite설정의 조합

Axios

api router에서 비동기 통신을 해야 하므로, axios를 사용 할 것인데 먼저, axios가 node.js에서 사용 할 수 있는지 찾아보았다.


출저 : Aixos 공식문서


Api Router Function만들기

req.body에 받아와야 하는 값은

  • LOCALDATA_020301_ : 동물병원의 query 값 중 하나
  • LOCALDATA_020302_ : 동물약국의 query 값 중 하나
  • LOCALDATA_020301(2)_${지역} : 지역구
  • LOCALDATA_020301(2)_${지역}/1/${범위} : 데이터 몇 개 호출 할 범위(최대 1000개)

한줄로 요약 하자면

animalHospitalAPI(`${LOCALDATA_020301_}${지역}/1/${범위}`)
animalPharamcyAPI(`${LOCALDATA_020302_}${지역}/1/${범위}`)

이렇게 되시겠다. 아래의 로직은 코드 전반이다.

//api/router~~
import axios from "axios";
import type { NextApiRequest, NextApiResponse } from 'next';

const animalHospitalAPI = axios.create({
  baseURL: `${process.env.NEXT_PUBLIC_ANIMAL_HOSPITAL}`,
});

const animalPharamcyAPI = axios.create({
  baseURL: `${process.env.NEXT_PUBLIC_ANIMAL_PHARAMCY}`,
});

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  const { api_type, query_type, area, endRange } = req.body;

  const api_fn = {
    animal_hospital: animalHospitalAPI,
    animal_pharmacy: animalPharamcyAPI,
  };
  try {
    const result = await api_fn[
      api_type as "animal_hospital" | "animal_pharmacy"
    ](`${query_type}${area}/1/${endRange}/01`);

    res.status(200).send({ data: result.data, query_string: query_type, area });
  } catch (err) {
    res.status(500).send(err);
  }
}

비동기 통신 함수 만들기 (useQuery + Promise.all)

저번 포스팅에서 동물병원&동물약국 API, 두 개의 API들을 동시에 호출 해야 하므로,
useQuery + Promise.all 조합으로 비동기 호출을 진행 했었는데, 그것을 수정해서 다시 만들었다.

  • LOCALDATA_020301_ : 동물병원의 query 값 중 하나
  • LOCALDATA_020302_ : 동물약국의 query 값 중 하나
/**
 *
 * @param api_query react-query에서 enabled와 지역구 query로 활용하는 인자 값입니다.
 * @returns
 */

const DYNAMIC_API_QURIES = [
  { api_type: "animal_hospital", query_type: "LOCALDATA_020301_" },
  { api_type: "animal_pharmacy", query_type: "LOCALDATA_020302_" },
];

// process.env.NEXT_PUBLIC_SEOUL_API_URL은 api router 주소입니다.
export const ParalledQueriesAnimalMedicineAPI = async (
  area: string | null,
  endRange: string,
) => {
  try {
    const results = await Promise.all(
      DYNAMIC_API_QURIES.map(async (query) => {
        const result = await axios.post(
          `${process.env.NEXT_PUBLIC_SEOUL_API_URL}`,
          {
            area,
            api_type: query.api_type, // api router에서 조건부 함수 처리위해서
            query_type: query.query_type, // 동물병원 || 동물약국 query
            endRange,
          },
        );
        return result.data;
      }),
    );

    return results;
  } catch (err) {
    console.log(err, "map Error");
    return []; // 성공 실패시 균일하게 해주기 위해서
  }
};

비동기 함수를 만들어주고 useQuery로 감싸주었다.

const MixedContent = ({ endRange }: { endRange: string }) => {
  const [query, setQuery] = useState<string | null>(null);
  const { data, isLoading } = useQuery({
    queryKey: [SEOUL_QUERY.MEDICINE, query],
    queryFn: () => ParalledQueriesAnimalMedicineAPI(query, endRange),
    enabled: !!query,
    select: refineSeoulApiData, // 원하는 데이터로 가공
    refetchOnWindowFocus: false,
    staleTime: Infinity,
  });

 
  return (
   <div style={{ display: "flex", gap: "20px" }}>
      {SEOUL_LOCATION.map((Area) => (
        <span
          onClick={() => setQuery(Area.api_query)}
          key={Area.location}
          style={{
            width: "80px",
            backgroundColor: "wheat",
            color: "black",
            height: "80px",
            textAlign: "center",
          }}
        >
          {Area.location}
        </span>
      ))}
     <div style={{ alignSelf: "center", fontSize: "24px" }}>
        데이터가 mixed content없이 불러와진{" "}
        <span
          style={{
            textDecoration: "underline",
            color: "bisque",
            fontSize: "32px",
          }}
        >
          {data ? data?.length : 0}
        </span>{" "}</div>
    </div>
  );
};

export default MixedContentError;

Nextjs config 설정 하기

끝났으면 이제 Nextjs config를 건드려보자

const nextConfig = {
  reactStrictMode: true,
  async rewrites() {
    return [
      {
        source: "/api/mixedError/mixedContents",
        destination: `${process.env.NEXT_PUBLIC_ANIMAL_HOSPITAL}`,
      },
      {
        source: "/api/mixedError/mixedContents",
        destination: `${process.env.NEXT_PUBLIC_ANIMAL_PHARAMCY}`,
      },
    ];
  },
};

// 동물병원 API
process.env.NEXT_PUBLIC_ANIMAL_HOSPITAL=http://openapi.seoul.go.kr:8088/secret-number/json/
// 동물약국 API
process.env.NEXT_PUBLIC_ANIMAL_PHARAMCY=http://openapi.seoul.go.kr:8088/secret-number/json/

source는 내가 api router(proxy)로 요청을 보내면 destination을 env 경로로 맵핑해주세요 이다.
이렇게 한 후 build 한 후 vercel 배포 후 한 번 확인해 보자

cf) 참고로 destination의 주소가 똑같은데 아래를 확인하면 불편한 마음이 사라질것이다.

build와 vercel배포

배포 후 확인을 해봤다.

성공했다.

refactor => 그러나 정확하게 개념을 안다면...

/api/mixedError/mixedContents로 경로를 요청했다면, 서버는 destination의 경로로 매핑하기에
사실상 2개를 쓸 의미가 없다. 그러므로

결국 mixed content에러는 상반된 프로토콜을 사용하기에 일어나는 것이다는 점을 인지하고,
rewrite로 http로 맵핑해주는 것이기에, 하나의 url로 통일 및,
서울시 공공데이터의 도메인만 적어줘도 문제가 없다.

따라서 Refactoring을 진행 하였다.

config 수정 refactor

As IS

 async rewrites() {
    return [
      {
        source: "/api/mixedError/mixedContents",
        destination: `${process.env.NEXT_PUBLIC_ANIMAL_HOSPITAL}`,
      },
      {
        source: "/api/mixedError/mixedContents",
        destination: `${process.env.NEXT_PUBLIC_ANIMAL_PHARAMCY}`,
      },
    ];
  },

To Be

// nextjs.config.mjs
{
  source: "/api/mixedError/mixedContents",
  destination: `${process.env.NEXT_PUBLIC_SEOUL_API_URL}`,
},
  
// As-Is
process.env.NEXT_PUBLIC_ANIMAL_HOSPITAL=http://openapi.seoul.go.kr:8088/secret-number/json/
// 동물약국 API
process.env.NEXT_PUBLIC_ANIMAL_PHARAMCY=http://openapi.seoul.go.kr:8088/secret-number/json/

// To-Be
NEXT_PUBLIC_SEOUL_API_URL=http://openapi.seoul.go.kr:8088/  

하나의 env로 바꿨는데,
2가지를 삭제해 주고, 아래와 같이 변경해주면 마음이 편안하다.

api router 함수 refactor

As IS

//api/mixed~~

//...생략
const animalHospitalAPI = axios.create({
  baseURL: `${process.env.NEXT_PUBLIC_ANIMAL_HOSPITAL}`,
});

const animalPharamcyAPI = axios.create({
  baseURL: `${process.env.NEXT_PUBLIC_ANIMAL_PHARAMCY}`,
});

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  const { api_type, query_type, area, endRange } = req.body;

  const api_fn = {
    animal_hospital: animalHospitalAPI,
    animal_pharmacy: animalPharamcyAPI,
  };
  try {
    const result = await api_fn[
      api_type as "animal_hospital" | "animal_pharmacy"
    ](`${query_type}${area}/1/${endRange}/01`);
  //...생략

To Be

// animalHospitalAPI과 animalPharamcyAPI를 통합
const animalMedicineAPI = axios.create({
  baseURL: `${process.env.NEXT_PUBLIC_ANIMAL_MEDICINE}`,
}); 

const { query_type, area, endRange } = req.body; // api_type삭제 
  try {
    // 조건으로 해주지 않아도 된다. 
    const result = await animalMedicineAPI(
      `${query_type}${area}/1/${endRange}/01`,
    );

api 함수 refactor

As Is

/**
 *
 * @param api_query react-query에서 enabled와 지역구 query로 활용하는 인자 값입니다.
 * @returns
 */

const DYNAMIC_API_QURIES = [
  { api_type: "animal_hospital", query_type: "LOCALDATA_020301_" },
  { api_type: "animal_pharmacy", query_type: "LOCALDATA_020302_" },
];
//... 생략

 const result = await axios.post(
          `${process.env.NEXT_PUBLIC_SEOUL_API_URL}`,
          {
            area,
            api_type: query.api_type, // api router에서 조건부 함수 처리위해서
            query_type: query.query_type, // 동물병원 || 동물약국 query
            endRange,
          },
        );

To Be

const DYNAMIC_API_QURIES = [
  { query_type: "LOCALDATA_020301_" },
  { query_type: "LOCALDATA_020302_" },
];

//... 생략
const result = await axios.post(
          `${process.env.NEXT_PUBLIC_MY_API_URL}`,
          {
            // api_type을 쓸 이유가 없어짐
            area,
            query_type: query.query_type, // 동물병원 || 동물약국 query
            endRange,
          },
        );
//...생략

마무리

mixed content에러를 통해서

  • Next.js에서 rewrite와 redirect를 공부,
  • http,https의 차이점과 중간자 공격을 공부하고 이해했다.

하나의 page에서 정말 많은 것을 배우고, 생각하고, 알아가면서, 문제를 해결해왔던 것같다.

지도 페이지에서 외부 API와 외부 라이브러리, 그리고 배포 후 문제까지 해결해가면서 정말 좋은 프로젝트 경험을 해온것 같다.

내가 작성한 코드는 블로그에 쓰기 위해 따로 작성한 코드이며 위의 블로그용 코드는 lunaxislu 여기서 확인이 가능합니다.

무언가 궁금하거나 잘못된 내용이 있으면 댓글 남겨주세요 !!

6개의 댓글

comment-user-thumbnail
2024년 7월 15일

async 함수에서 try catch를 쓴 이유가 있나요? await then으로도 해결가능해보이는데

1개의 답글

관련 채용 정보